diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39f202f..3ec42a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,6 +49,7 @@ jobs: - uses: ros-tooling/action-ros-ci@master with: package-name: > + lttngpy ros2trace test_ros2trace test_tracetools diff --git a/LTTng_QUALITY_DECLARATION.md b/LTTng_QUALITY_DECLARATION.md index 5fc1225..bc3a728 100644 --- a/LTTng_QUALITY_DECLARATION.md +++ b/LTTng_QUALITY_DECLARATION.md @@ -88,16 +88,21 @@ Features are listed and well documented on the [LTTng website](https://lttng.org LTTng packages have embedded API documentation. It can be viewed on their man pages: -* [`lttng-ust`](https://lttng.org/man/3/lttng-ust/v2.11/) +* [`lttng-ust`](https://lttng.org/man/3/lttng-ust/v2.13/) ### License [3.iii] All repositories have a `LICENSE` file. All relevant files have a license identifier. -* `lttng-tools` is licensed under LGPLv2.1 and GPLv2, see [`LICENSE` file](https://github.com/lttng/lttng-tools/blob/master/LICENSE) * `lttng-ust` is licensed under LGPLv2.1, the MIT license and GPLv2, see [`LICENSE` file](https://github.com/lttng/lttng-ust/blob/master/LICENSE) + * `liblttng-ust` (build dependency) is LGPLv2.1 and MIT + * The rest (runtime tools, not dependencies) is GPLv2 +* `lttng-tools` is licensed under LGPLv2.1 and GPLv2, see [`LICENSE` file](https://github.com/lttng/lttng-tools/blob/master/LICENSE) + * `liblttng-ctl` (build dependency) is LGPLv2.1 + * The rest (runtime tools, not dependencies) is GPLv2 * `lttng-modules` is licensed under LGPLv2.1, GPLv2 and the MIT license, see [`LICENSE` file](https://github.com/lttng/lttng-modules/blob/master/LICENSE) + * Not a dependency ### Copyright Statement [3.iv] diff --git a/README.md b/README.md index 9079093..cf56ce5 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,10 @@ The LTTng kernel tracer has a similar implementation, but is separate from the u ## Packages +### lttngpy + +Package containing `liblttng-ctl` Python bindings. + ### ros2trace Package containing a `ros2cli` extension to enable tracing. @@ -246,6 +250,10 @@ Package containing tools for tracing-related tests. Package containing tools to enable tracing. +### test_ros2trace + +Package containing system tests for `ros2trace`. + ### test_tracetools Package containing unit and system tests for `tracetools`. diff --git a/lttngpy/.gitignore b/lttngpy/.gitignore new file mode 100644 index 0000000..2f836aa --- /dev/null +++ b/lttngpy/.gitignore @@ -0,0 +1,2 @@ +*~ +*.pyc diff --git a/lttngpy/CMakeLists.txt b/lttngpy/CMakeLists.txt new file mode 100644 index 0000000..0ea02cb --- /dev/null +++ b/lttngpy/CMakeLists.txt @@ -0,0 +1,149 @@ +cmake_minimum_required(VERSION 3.12) + +project(lttngpy) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() +# Default to C11 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 11) +endif() +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) + +if(WIN32 OR APPLE OR ANDROID) + set(DISABLED_DEFAULT ON) +else() + set(DISABLED_DEFAULT OFF) +endif() +option( + LTTNGPY_DISABLED + "Explicitly disable support, don't link against liblttng-ctl" + ${DISABLED_DEFAULT}) + +# Find python before pybind11 +find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + +find_package(pybind11_vendor REQUIRED) +find_package(pybind11 REQUIRED) + +if(NOT LTTNGPY_DISABLED) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LTTNG_CTL REQUIRED lttng-ctl) + set(LTTNG_CTL_VERSION ${LTTNG_CTL_VERSION}) + + string(REGEX MATCH "([0-9]+)\.([0-9]+)\.([0-9]+)" dummy "${LTTNG_CTL_VERSION}") + set(LTTNG_CTL_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(LTTNG_CTL_VERSION_MINOR "${CMAKE_MATCH_2}") + set(LTTNG_CTL_VERSION_PATCH "${CMAKE_MATCH_3}") +else() + set(LTTNG_CTL_VERSION "") + set(LTTNG_CTL_VERSION_MAJOR "0") + set(LTTNG_CTL_VERSION_MINOR "0") + set(LTTNG_CTL_VERSION_PATCH "0") +endif() + +# Store configuration variable for buildtime use +# LTTNGPY_DISABLED +# LTTNG_CTL_VERSION +# LTTNG_CTL_VERSION_MAJOR +# LTTNG_CTL_VERSION_MINOR +# LTTNG_CTL_VERSION_PATCH +configure_file(src/lttngpy/config.hpp.in src/lttngpy/config.hpp) + +ament_python_install_package(${PROJECT_NAME}) + +set(SOURCES + src/lttngpy/_lttngpy_pybind11.cpp + src/lttngpy/status.cpp +) +if(NOT LTTNGPY_DISABLED) + list(APPEND SOURCES + src/lttngpy/channel.cpp + src/lttngpy/context_app.cpp + src/lttngpy/context_lttng.cpp + src/lttngpy/context_perf.cpp + src/lttngpy/event.cpp + src/lttngpy/lttng.cpp + src/lttngpy/session.cpp + ) +endif() + +pybind11_add_module(_lttngpy_pybind11 SHARED ${SOURCES}) + +if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT APPLE) + target_link_libraries(_lttngpy_pybind11 PRIVATE atomic) +endif() + +target_include_directories(_lttngpy_pybind11 PRIVATE + "$" + "$" +) +if(NOT LTTNGPY_DISABLED) + target_link_libraries(_lttngpy_pybind11 PRIVATE ${LTTNG_CTL_LIBRARIES}) +endif() + +# Set the build location and install location for a CPython extension +install(TARGETS _lttngpy_pybind11 + DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}" +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + + if(NOT LTTNGPY_DISABLED) + find_package(ament_cmake_gtest REQUIRED) + find_package(ament_cmake_pytest REQUIRED) + + # Using source files, because I can't seem to be able to link against _lttngpy_pybind11 + ament_add_gtest(test_context_app test/test_context_app.cpp src/lttngpy/context_app.cpp) + if(TARGET test_context_app) + target_include_directories(test_context_app PRIVATE + "$" + "$" + ) + endif() + + ament_add_gtest(test_context_lttng test/test_context_lttng.cpp src/lttngpy/context_lttng.cpp) + if(TARGET test_context_lttng) + target_link_libraries(test_context_lttng ${LTTNG_CTL_LIBRARIES}) + target_include_directories(test_context_lttng PRIVATE + "$" + "$" + ) + endif() + + ament_add_gtest(test_context_perf test/test_context_perf.cpp src/lttngpy/context_perf.cpp) + if(TARGET test_context_perf) + target_link_libraries(test_context_perf ${LTTNG_CTL_LIBRARIES}) + target_include_directories(test_context_perf PRIVATE + "$" + "$" + ) + endif() + + set(_lttngpy_pytest_tests + test/test_constants.py + test/test_session.py + ) + + foreach(_test_path ${_lttngpy_pytest_tests}) + get_filename_component(_test_name ${_test_path} NAME_WE) + ament_add_pytest_test(${_test_name} ${_test_path} + APPEND_ENV AMENT_PREFIX_PATH=${ament_index_build_path} + PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR} + TIMEOUT 120 + WERROR ON + ) + endforeach() + endif() +endif() + +ament_package() diff --git a/lttngpy/lttngpy/__init__.py b/lttngpy/lttngpy/__init__.py new file mode 100644 index 0000000..fdee7d2 --- /dev/null +++ b/lttngpy/lttngpy/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2023 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .impl import impl + + +__all__ = [ + 'impl', +] diff --git a/lttngpy/lttngpy/impl.py b/lttngpy/lttngpy/impl.py new file mode 100644 index 0000000..1f38137 --- /dev/null +++ b/lttngpy/lttngpy/impl.py @@ -0,0 +1,18 @@ +# Copyright 2023 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rpyutils import import_c_library + + +impl = import_c_library('._lttngpy_pybind11', 'lttngpy') diff --git a/lttngpy/package.xml b/lttngpy/package.xml new file mode 100644 index 0000000..6073f3d --- /dev/null +++ b/lttngpy/package.xml @@ -0,0 +1,31 @@ + + + + lttngpy + 7.1.0 + liblttng-ctl Python bindings + Christophe Bedard + Apache License 2.0 + https://index.ros.org/p/lttngpy/ + https://github.com/ros2/ros2_tracing + https://github.com/ros2/ros2_tracing/issues + Christophe Bedard + + ament_cmake + python_cmake_module + + liblttng-ctl-dev + + pybind11_vendor + + rpyutils + + ament_cmake_gtest + ament_cmake_pytest + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp b/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp new file mode 100644 index 0000000..8777fa8 --- /dev/null +++ b/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp @@ -0,0 +1,344 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "lttngpy/config.hpp" + +#ifndef LTTNGPY_DISABLED +# include +#endif // LTTNGPY_DISABLED +#include +#include + +#ifndef LTTNGPY_DISABLED +# include "lttngpy/channel.hpp" +# include "lttngpy/event.hpp" +# include "lttngpy/lttng.hpp" +# include "lttngpy/session.hpp" +#endif // LTTNGPY_DISABLED +#include "lttngpy/status.hpp" + +namespace py = pybind11; + +PYBIND11_MODULE(_lttngpy_pybind11, m) { + /** + * Some lttng-ctl functions and constants are directly exposed, others are wrapped (e.g., to make + * the interface more Pythonic/use Python types) and the wrapper functions are exposed. The + * exposed lttng-ctl functions and constants usually start with lttng_* or LTTNG_*, respectively. + */ + m.doc() = "LTTng trace control bindings"; + + // Status + m.def( + "is_available", + <tngpy::is_available, + "Check if lttng-ctl is available. If False, then no other functions are available."); + +#ifndef LTTNGPY_DISABLED + // General + m.attr("LTTNG_CTL_VERSION") = LTTNG_CTL_VERSION; + + // Session daemon + m.def( + "lttng_session_daemon_alive", + <tng_session_daemon_alive, + "Check if a session daemon is alive."); + m.def( + "is_lttng_session_daemon_alive", + <tngpy::is_lttng_session_daemon_alive, + "Check if a session daemon is alive."); + + // Session creation/destruction + m.def( + "lttng_create_session", + <tng_create_session, + "Create session.", + py::kw_only(), + py::arg("session_name"), + py::arg("url")); + m.def( + "lttng_destroy_session", + <tng_destroy_session, + "Destroy session.", + py::kw_only(), + py::arg("session_name")); + m.def( + "destroy_all_sessions", + <tngpy::destroy_all_sessions, + "Destroy all sessions."); + + // Session control + m.def( + "lttng_start_tracing", + <tng_start_tracing, + "Start tracing.", + py::kw_only(), + py::arg("session_name")); + m.def( + "lttng_stop_tracing", + <tng_stop_tracing, + "Stop tracing.", + py::kw_only(), + py::arg("session_name")); + + // Session info + m.def( + "get_session_names", + <tngpy::get_session_names, + "Get the currently-existing session names."); + + // Domain + py::enum_(m, "lttng_domain_type") + .value("LTTNG_DOMAIN_NONE", LTTNG_DOMAIN_NONE) + .value("LTTNG_DOMAIN_KERNEL", LTTNG_DOMAIN_KERNEL) + .value("LTTNG_DOMAIN_UST", LTTNG_DOMAIN_UST) + .value("LTTNG_DOMAIN_JUL", LTTNG_DOMAIN_JUL) + .value("LTTNG_DOMAIN_LOG4J", LTTNG_DOMAIN_LOG4J) + .value("LTTNG_DOMAIN_PYTHON", LTTNG_DOMAIN_PYTHON) + .export_values(); + py::enum_(m, "lttng_buffer_type") + .value("LTTNG_BUFFER_PER_PID", LTTNG_BUFFER_PER_PID) + .value("LTTNG_BUFFER_PER_UID", LTTNG_BUFFER_PER_UID) + .value("LTTNG_BUFFER_GLOBAL", LTTNG_BUFFER_GLOBAL) + .export_values(); + + // Channel + m.def( + "enable_channel", + <tngpy::enable_channel, + "Enable channel.", + py::kw_only(), + py::arg("session_name"), + py::arg("domain_type"), + py::arg("buffer_type"), + py::arg("channel_name"), + py::arg("overwrite"), + py::arg("subbuf_size"), + py::arg("num_subbuf"), + py::arg("switch_timer_interval"), + py::arg("read_timer_interval"), + py::arg("output")); + + // Event + py::enum_(m, "lttng_event_output") + .value("LTTNG_EVENT_SPLICE", LTTNG_EVENT_SPLICE) + .value("LTTNG_EVENT_MMAP", LTTNG_EVENT_MMAP) + .export_values(); + m.def( + "enable_events", + <tngpy::enable_events, + "Enable event.", + py::kw_only(), + py::arg("session_name"), + py::arg("domain_type"), + py::arg("channel_name"), + py::arg("events")); + m.def( + "get_tracepoints", + <tngpy::get_tracepoints, + "Get tracepoints.", + py::kw_only(), + py::arg("domain_type")); + m.def( + "add_contexts", + <tngpy::add_contexts, + "Add contexts.", + py::kw_only(), + py::arg("session_name"), + py::arg("domain_type"), + py::arg("channel_name"), + py::arg("context_fields")); + + // Error + m.def( + "lttng_strerror", + <tng_strerror, + "Get string representation of (negative) error code.", + py::arg("code")); + py::enum_(m, "lttng_error_code") + .value("LTTNG_OK", LTTNG_OK) + .value("LTTNG_ERR_UNK", LTTNG_ERR_UNK) + .value("LTTNG_ERR_UND", LTTNG_ERR_UND) + .value("LTTNG_ERR_SESSION_STARTED", LTTNG_ERR_SESSION_STARTED) + .value("LTTNG_ERR_UNKNOWN_DOMAIN", LTTNG_ERR_UNKNOWN_DOMAIN) + .value("LTTNG_ERR_NOT_SUPPORTED", LTTNG_ERR_NOT_SUPPORTED) + .value("LTTNG_ERR_NO_SESSION", LTTNG_ERR_NO_SESSION) + .value("LTTNG_ERR_CREATE_DIR_FAIL", LTTNG_ERR_CREATE_DIR_FAIL) + .value("LTTNG_ERR_SESSION_FAIL", LTTNG_ERR_SESSION_FAIL) + .value("LTTNG_ERR_NO_SESSIOND", LTTNG_ERR_NO_SESSIOND) + .value("LTTNG_ERR_SET_URL", LTTNG_ERR_SET_URL) + .value("LTTNG_ERR_URL_EXIST", LTTNG_ERR_URL_EXIST) + .value("LTTNG_ERR_BUFFER_NOT_SUPPORTED", LTTNG_ERR_BUFFER_NOT_SUPPORTED) + .value("LTTNG_ERR_SESS_NOT_FOUND", LTTNG_ERR_SESS_NOT_FOUND) + .value("LTTNG_ERR_BUFFER_TYPE_MISMATCH", LTTNG_ERR_BUFFER_TYPE_MISMATCH) + .value("LTTNG_ERR_FATAL", LTTNG_ERR_FATAL) + .value("LTTNG_ERR_NOMEM", LTTNG_ERR_NOMEM) + .value("LTTNG_ERR_SELECT_SESS", LTTNG_ERR_SELECT_SESS) + .value("LTTNG_ERR_EXIST_SESS", LTTNG_ERR_EXIST_SESS) + .value("LTTNG_ERR_NO_EVENT", LTTNG_ERR_NO_EVENT) + .value("LTTNG_ERR_CONNECT_FAIL", LTTNG_ERR_CONNECT_FAIL) + .value("LTTNG_ERR_SNAPSHOT_OUTPUT_EXIST", LTTNG_ERR_SNAPSHOT_OUTPUT_EXIST) + .value("LTTNG_ERR_EPERM", LTTNG_ERR_EPERM) + .value("LTTNG_ERR_KERN_NA", LTTNG_ERR_KERN_NA) + .value("LTTNG_ERR_KERN_VERSION", LTTNG_ERR_KERN_VERSION) + .value("LTTNG_ERR_KERN_EVENT_EXIST", LTTNG_ERR_KERN_EVENT_EXIST) + .value("LTTNG_ERR_KERN_SESS_FAIL", LTTNG_ERR_KERN_SESS_FAIL) + .value("LTTNG_ERR_KERN_CHAN_EXIST", LTTNG_ERR_KERN_CHAN_EXIST) + .value("LTTNG_ERR_KERN_CHAN_FAIL", LTTNG_ERR_KERN_CHAN_FAIL) + .value("LTTNG_ERR_KERN_CHAN_NOT_FOUND", LTTNG_ERR_KERN_CHAN_NOT_FOUND) + .value("LTTNG_ERR_KERN_CHAN_DISABLE_FAIL", LTTNG_ERR_KERN_CHAN_DISABLE_FAIL) + .value("LTTNG_ERR_KERN_CHAN_ENABLE_FAIL", LTTNG_ERR_KERN_CHAN_ENABLE_FAIL) + .value("LTTNG_ERR_KERN_CONTEXT_FAIL", LTTNG_ERR_KERN_CONTEXT_FAIL) + .value("LTTNG_ERR_KERN_ENABLE_FAIL", LTTNG_ERR_KERN_ENABLE_FAIL) + .value("LTTNG_ERR_KERN_DISABLE_FAIL", LTTNG_ERR_KERN_DISABLE_FAIL) + .value("LTTNG_ERR_KERN_META_FAIL", LTTNG_ERR_KERN_META_FAIL) + .value("LTTNG_ERR_KERN_START_FAIL", LTTNG_ERR_KERN_START_FAIL) + .value("LTTNG_ERR_KERN_STOP_FAIL", LTTNG_ERR_KERN_STOP_FAIL) + .value("LTTNG_ERR_KERN_CONSUMER_FAIL", LTTNG_ERR_KERN_CONSUMER_FAIL) + .value("LTTNG_ERR_KERN_STREAM_FAIL", LTTNG_ERR_KERN_STREAM_FAIL) + .value("LTTNG_ERR_START_SESSION_ONCE", LTTNG_ERR_START_SESSION_ONCE) + .value("LTTNG_ERR_SNAPSHOT_FAIL", LTTNG_ERR_SNAPSHOT_FAIL) + .value("LTTNG_ERR_NO_STREAM", LTTNG_ERR_NO_STREAM) + .value("LTTNG_ERR_KERN_LIST_FAIL", LTTNG_ERR_KERN_LIST_FAIL) + .value("LTTNG_ERR_UST_CALIBRATE_FAIL", LTTNG_ERR_UST_CALIBRATE_FAIL) + .value("LTTNG_ERR_UST_EVENT_ENABLED", LTTNG_ERR_UST_EVENT_ENABLED) + .value("LTTNG_ERR_UST_SESS_FAIL", LTTNG_ERR_UST_SESS_FAIL) + .value("LTTNG_ERR_UST_CHAN_EXIST", LTTNG_ERR_UST_CHAN_EXIST) + .value("LTTNG_ERR_UST_CHAN_FAIL", LTTNG_ERR_UST_CHAN_FAIL) + .value("LTTNG_ERR_UST_CHAN_NOT_FOUND", LTTNG_ERR_UST_CHAN_NOT_FOUND) + .value("LTTNG_ERR_UST_CHAN_DISABLE_FAIL", LTTNG_ERR_UST_CHAN_DISABLE_FAIL) + .value("LTTNG_ERR_UST_CHAN_ENABLE_FAIL", LTTNG_ERR_UST_CHAN_ENABLE_FAIL) + .value("LTTNG_ERR_CHAN_EXIST", LTTNG_ERR_CHAN_EXIST) + .value("LTTNG_ERR_UST_ENABLE_FAIL", LTTNG_ERR_UST_ENABLE_FAIL) + .value("LTTNG_ERR_UST_DISABLE_FAIL", LTTNG_ERR_UST_DISABLE_FAIL) + .value("LTTNG_ERR_UST_META_FAIL", LTTNG_ERR_UST_META_FAIL) + .value("LTTNG_ERR_UST_START_FAIL", LTTNG_ERR_UST_START_FAIL) + .value("LTTNG_ERR_UST_STOP_FAIL", LTTNG_ERR_UST_STOP_FAIL) + .value("LTTNG_ERR_UST_CONSUMER64_FAIL", LTTNG_ERR_UST_CONSUMER64_FAIL) + .value("LTTNG_ERR_UST_CONSUMER32_FAIL", LTTNG_ERR_UST_CONSUMER32_FAIL) + .value("LTTNG_ERR_UST_STREAM_FAIL", LTTNG_ERR_UST_STREAM_FAIL) + .value("LTTNG_ERR_SNAPSHOT_NODATA", LTTNG_ERR_SNAPSHOT_NODATA) + .value("LTTNG_ERR_NO_CHANNEL", LTTNG_ERR_NO_CHANNEL) + .value("LTTNG_ERR_SESSION_INVALID_CHAR", LTTNG_ERR_SESSION_INVALID_CHAR) + .value("LTTNG_ERR_UST_LIST_FAIL", LTTNG_ERR_UST_LIST_FAIL) + .value("LTTNG_ERR_UST_EVENT_EXIST", LTTNG_ERR_UST_EVENT_EXIST) + .value("LTTNG_ERR_UST_EVENT_NOT_FOUND", LTTNG_ERR_UST_EVENT_NOT_FOUND) + .value("LTTNG_ERR_UST_CONTEXT_EXIST", LTTNG_ERR_UST_CONTEXT_EXIST) + .value("LTTNG_ERR_UST_CONTEXT_INVAL", LTTNG_ERR_UST_CONTEXT_INVAL) + .value("LTTNG_ERR_NEED_ROOT_SESSIOND", LTTNG_ERR_NEED_ROOT_SESSIOND) + .value("LTTNG_ERR_TRACE_ALREADY_STARTED", LTTNG_ERR_TRACE_ALREADY_STARTED) + .value("LTTNG_ERR_TRACE_ALREADY_STOPPED", LTTNG_ERR_TRACE_ALREADY_STOPPED) + .value("LTTNG_ERR_KERN_EVENT_ENOSYS", LTTNG_ERR_KERN_EVENT_ENOSYS) + .value("LTTNG_ERR_NEED_CHANNEL_NAME", LTTNG_ERR_NEED_CHANNEL_NAME) + .value("LTTNG_ERR_NO_UST", LTTNG_ERR_NO_UST) + .value("LTTNG_ERR_SAVE_FILE_EXIST", LTTNG_ERR_SAVE_FILE_EXIST) + .value("LTTNG_ERR_SAVE_IO_FAIL", LTTNG_ERR_SAVE_IO_FAIL) + .value("LTTNG_ERR_LOAD_INVALID_CONFIG", LTTNG_ERR_LOAD_INVALID_CONFIG) + .value("LTTNG_ERR_LOAD_IO_FAIL", LTTNG_ERR_LOAD_IO_FAIL) + .value("LTTNG_ERR_LOAD_SESSION_NOENT", LTTNG_ERR_LOAD_SESSION_NOENT) + .value("LTTNG_ERR_MAX_SIZE_INVALID", LTTNG_ERR_MAX_SIZE_INVALID) + .value("LTTNG_ERR_MI_OUTPUT_TYPE", LTTNG_ERR_MI_OUTPUT_TYPE) + .value("LTTNG_ERR_MI_IO_FAIL", LTTNG_ERR_MI_IO_FAIL) + .value("LTTNG_ERR_MI_NOT_IMPLEMENTED", LTTNG_ERR_MI_NOT_IMPLEMENTED) + .value("LTTNG_ERR_INVALID", LTTNG_ERR_INVALID) + .value("LTTNG_ERR_NO_USTCONSUMERD", LTTNG_ERR_NO_USTCONSUMERD) + .value("LTTNG_ERR_NO_KERNCONSUMERD", LTTNG_ERR_NO_KERNCONSUMERD) + .value("LTTNG_ERR_EVENT_EXIST_LOGLEVEL", LTTNG_ERR_EVENT_EXIST_LOGLEVEL) + .value("LTTNG_ERR_URL_DATA_MISS", LTTNG_ERR_URL_DATA_MISS) + .value("LTTNG_ERR_URL_CTRL_MISS", LTTNG_ERR_URL_CTRL_MISS) + .value("LTTNG_ERR_ENABLE_CONSUMER_FAIL", LTTNG_ERR_ENABLE_CONSUMER_FAIL) + .value("LTTNG_ERR_RELAYD_CONNECT_FAIL", LTTNG_ERR_RELAYD_CONNECT_FAIL) + .value("LTTNG_ERR_RELAYD_VERSION_FAIL", LTTNG_ERR_RELAYD_VERSION_FAIL) + .value("LTTNG_ERR_FILTER_INVAL", LTTNG_ERR_FILTER_INVAL) + .value("LTTNG_ERR_FILTER_NOMEM", LTTNG_ERR_FILTER_NOMEM) + .value("LTTNG_ERR_FILTER_EXIST", LTTNG_ERR_FILTER_EXIST) + .value("LTTNG_ERR_NO_CONSUMER", LTTNG_ERR_NO_CONSUMER) + .value("LTTNG_ERR_EXCLUSION_INVAL", LTTNG_ERR_EXCLUSION_INVAL) + .value("LTTNG_ERR_EXCLUSION_NOMEM", LTTNG_ERR_EXCLUSION_NOMEM) + .value("LTTNG_ERR_INVALID_EVENT_NAME", LTTNG_ERR_INVALID_EVENT_NAME) + .value("LTTNG_ERR_INVALID_CHANNEL_NAME", LTTNG_ERR_INVALID_CHANNEL_NAME) + .value("LTTNG_ERR_PROCESS_ATTR_EXISTS", LTTNG_ERR_PROCESS_ATTR_EXISTS) + .value("LTTNG_ERR_PROCESS_ATTR_MISSING", LTTNG_ERR_PROCESS_ATTR_MISSING) + .value("LTTNG_ERR_INVALID_CHANNEL_DOMAIN", LTTNG_ERR_INVALID_CHANNEL_DOMAIN) + .value("LTTNG_ERR_OVERFLOW", LTTNG_ERR_OVERFLOW) + .value("LTTNG_ERR_SESSION_NOT_STARTED", LTTNG_ERR_SESSION_NOT_STARTED) + .value("LTTNG_ERR_LIVE_SESSION", LTTNG_ERR_LIVE_SESSION) + .value("LTTNG_ERR_PER_PID_SESSION", LTTNG_ERR_PER_PID_SESSION) + .value("LTTNG_ERR_KERN_CONTEXT_UNAVAILABLE", LTTNG_ERR_KERN_CONTEXT_UNAVAILABLE) + .value("LTTNG_ERR_REGEN_STATEDUMP_FAIL", LTTNG_ERR_REGEN_STATEDUMP_FAIL) + .value("LTTNG_ERR_REGEN_STATEDUMP_NOMEM", LTTNG_ERR_REGEN_STATEDUMP_NOMEM) + .value("LTTNG_ERR_NOT_SNAPSHOT_SESSION", LTTNG_ERR_NOT_SNAPSHOT_SESSION) + .value("LTTNG_ERR_INVALID_TRIGGER", LTTNG_ERR_INVALID_TRIGGER) + .value("LTTNG_ERR_TRIGGER_EXISTS", LTTNG_ERR_TRIGGER_EXISTS) + .value("LTTNG_ERR_TRIGGER_NOT_FOUND", LTTNG_ERR_TRIGGER_NOT_FOUND) + .value("LTTNG_ERR_COMMAND_CANCELLED", LTTNG_ERR_COMMAND_CANCELLED) + .value("LTTNG_ERR_ROTATION_PENDING", LTTNG_ERR_ROTATION_PENDING) + .value("LTTNG_ERR_ROTATION_NOT_AVAILABLE", LTTNG_ERR_ROTATION_NOT_AVAILABLE) + .value("LTTNG_ERR_ROTATION_SCHEDULE_SET", LTTNG_ERR_ROTATION_SCHEDULE_SET) + .value("LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET", LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET) + .value("LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP", LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP) + .value("LTTNG_ERR_ROTATION_WRONG_VERSION", LTTNG_ERR_ROTATION_WRONG_VERSION) + .value("LTTNG_ERR_NO_SESSION_OUTPUT", LTTNG_ERR_NO_SESSION_OUTPUT) + .value("LTTNG_ERR_ROTATION_NOT_AVAILABLE_RELAY", LTTNG_ERR_ROTATION_NOT_AVAILABLE_RELAY) + .value("LTTNG_ERR_AGENT_TRACING_DISABLED", LTTNG_ERR_AGENT_TRACING_DISABLED) + .value("LTTNG_ERR_PROBE_LOCATION_INVAL", LTTNG_ERR_PROBE_LOCATION_INVAL) + .value("LTTNG_ERR_ELF_PARSING", LTTNG_ERR_ELF_PARSING) + .value("LTTNG_ERR_SDT_PROBE_SEMAPHORE", LTTNG_ERR_SDT_PROBE_SEMAPHORE) + .value("LTTNG_ERR_ROTATION_FAIL_CONSUMER", LTTNG_ERR_ROTATION_FAIL_CONSUMER) + .value("LTTNG_ERR_ROTATE_RENAME_FAIL_CONSUMER", LTTNG_ERR_ROTATE_RENAME_FAIL_CONSUMER) + .value( + "LTTNG_ERR_ROTATION_PENDING_LOCAL_FAIL_CONSUMER", + LTTNG_ERR_ROTATION_PENDING_LOCAL_FAIL_CONSUMER) + .value( + "LTTNG_ERR_ROTATION_PENDING_RELAY_FAIL_CONSUMER", + LTTNG_ERR_ROTATION_PENDING_RELAY_FAIL_CONSUMER) + .value("LTTNG_ERR_MKDIR_FAIL_CONSUMER", LTTNG_ERR_MKDIR_FAIL_CONSUMER) + .value("LTTNG_ERR_CHAN_NOT_FOUND", LTTNG_ERR_CHAN_NOT_FOUND) + .value("LTTNG_ERR_SNAPSHOT_UNSUPPORTED", LTTNG_ERR_SNAPSHOT_UNSUPPORTED) + .value("LTTNG_ERR_SESSION_NOT_EXIST", LTTNG_ERR_SESSION_NOT_EXIST) + .value("LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER", LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER) + .value("LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER", LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER) + .value("LTTNG_ERR_TRACE_CHUNK_EXISTS_FAIL_CONSUMER", LTTNG_ERR_TRACE_CHUNK_EXISTS_FAIL_CONSUMER) + .value("LTTNG_ERR_INVALID_PROTOCOL", LTTNG_ERR_INVALID_PROTOCOL) + .value("LTTNG_ERR_FILE_CREATION_ERROR", LTTNG_ERR_FILE_CREATION_ERROR) + .value("LTTNG_ERR_TIMER_STOP_ERROR", LTTNG_ERR_TIMER_STOP_ERROR) + .value("LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL", LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL) + .value("LTTNG_ERR_CLEAR_RELAY_DISALLOWED", LTTNG_ERR_CLEAR_RELAY_DISALLOWED) + .value("LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY", LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY) + .value("LTTNG_ERR_CLEAR_FAIL_CONSUMER", LTTNG_ERR_CLEAR_FAIL_CONSUMER) + .value("LTTNG_ERR_ROTATION_AFTER_STOP_CLEAR", LTTNG_ERR_ROTATION_AFTER_STOP_CLEAR) + .value("LTTNG_ERR_USER_NOT_FOUND", LTTNG_ERR_USER_NOT_FOUND) + .value("LTTNG_ERR_GROUP_NOT_FOUND", LTTNG_ERR_GROUP_NOT_FOUND) + .value("LTTNG_ERR_UNSUPPORTED_DOMAIN", LTTNG_ERR_UNSUPPORTED_DOMAIN) + .value( + "LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY", + LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY) +#if (LTTNG_CTL_VERSION_MAJOR >= 2) && (LTTNG_CTL_VERSION_MINOR >= 13) + .value( + "LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD", + LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD) + .value("LTTNG_ERR_INVALID_CAPTURE_EXPRESSION", LTTNG_ERR_INVALID_CAPTURE_EXPRESSION) + .value("LTTNG_ERR_EVENT_NOTIFIER_REGISTRATION", LTTNG_ERR_EVENT_NOTIFIER_REGISTRATION) + .value("LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING", LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING) + .value( + "LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING_FULL", + LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING_FULL) + .value("LTTNG_ERR_INVALID_ERROR_QUERY_TARGET", LTTNG_ERR_INVALID_ERROR_QUERY_TARGET) + .value("LTTNG_ERR_BUFFER_FLUSH_FAILED", LTTNG_ERR_BUFFER_FLUSH_FAILED) +#endif // (LTTNG_CTL_VERSION_MAJOR >= 2) && (LTTNG_CTL_VERSION_MINOR >= 13) + .value("LTTNG_ERR_NR", LTTNG_ERR_NR) + .value("LTTNG_ERR_PID_TRACKED", LTTNG_ERR_PID_TRACKED) + .value("LTTNG_ERR_PID_NOT_TRACKED", LTTNG_ERR_PID_NOT_TRACKED) + .export_values(); +#endif // LTTNGPY_DISABLED +} diff --git a/lttngpy/src/lttngpy/channel.cpp b/lttngpy/src/lttngpy/channel.cpp new file mode 100644 index 0000000..1402f31 --- /dev/null +++ b/lttngpy/src/lttngpy/channel.cpp @@ -0,0 +1,87 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lttngpy/channel.hpp" + +namespace lttngpy +{ + +int enable_channel( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const enum lttng_buffer_type buffer_type, + const std::string & channel_name, + const std::optional overwrite, + const std::optional subbuf_size, + const std::optional num_subbuf, + const std::optional switch_timer_interval, + const std::optional read_timer_interval, + const std::optional output) +{ + struct lttng_domain domain {}; + domain.type = domain_type; + domain.buf_type = buffer_type; + + struct lttng_handle * handle = lttng_create_handle(session_name.c_str(), &domain); + if (nullptr == handle) { + return -LTTNG_ERR_UNK; + } + + struct lttng_channel * chan = lttng_channel_create(&domain); + if (nullptr == chan) { + return -LTTNG_ERR_UNK; + } + /** + * Configure channel. + * + * lttng_channel_create() sets default values for the attributes depending on the domain type + * and/or buffer type, so set them only if needed. + */ + channel_name.copy(chan->name, LTTNG_SYMBOL_NAME_LEN); + if (overwrite.has_value()) { + chan->attr.overwrite = overwrite.value(); + } + if (subbuf_size.has_value()) { + chan->attr.subbuf_size = subbuf_size.value(); + } + if (num_subbuf.has_value()) { + chan->attr.num_subbuf = num_subbuf.value(); + } + if (switch_timer_interval.has_value()) { + chan->attr.switch_timer_interval = switch_timer_interval.value(); + } + if (read_timer_interval.has_value()) { + chan->attr.read_timer_interval = read_timer_interval.value(); + } + if (output.has_value()) { + chan->attr.output = output.value(); + } + + int ret = lttng_enable_channel(handle, chan); + lttng_channel_destroy(chan); + lttng_destroy_handle(handle); + return ret; +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/channel.hpp b/lttngpy/src/lttngpy/channel.hpp new file mode 100644 index 0000000..bfc6f03 --- /dev/null +++ b/lttngpy/src/lttngpy/channel.hpp @@ -0,0 +1,57 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__CHANNEL_HPP_ +#define LTTNGPY__CHANNEL_HPP_ + +#include + +#include + +namespace lttngpy +{ + +/** + * Enable channel. + * + * \param session_name the session name + * \param domain_type the domain type + * \param buffer_type the buffer type + * \param channel_name the channel name + * \param overwrite the overwrite attribute (-1: session default, 1: overwrite, 0: discard), or + * `std::nullopt` for the default value + * \param subbuf_size the subbuffer size in bytes (power of 2), or `std::nullopt` for the default + * value + * \param num_subbuf the number of subbuffers (power of 2), or `std::nullopt` for the default value + * \param switch_timer_interval the switch timer internal in usec (0 to disable), or `std::nullopt` + * for the default value + * \param read_timer_interval the read timer internal in usec (0 to disable), or `std::nullopt` for + * the default value + * \return 0 on success, else a negative LTTng error code + */ +int enable_channel( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const enum lttng_buffer_type buffer_type, + const std::string & channel_name, + const std::optional overwrite, + const std::optional subbuf_size, + const std::optional num_subbuf, + const std::optional switch_timer_interval, + const std::optional read_timer_interval, + const std::optional output); + +} // namespace lttngpy + +#endif // LTTNGPY__CHANNEL_HPP_ diff --git a/lttngpy/src/lttngpy/config.hpp.in b/lttngpy/src/lttngpy/config.hpp.in new file mode 100644 index 0000000..dd25aca --- /dev/null +++ b/lttngpy/src/lttngpy/config.hpp.in @@ -0,0 +1,24 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__CONFIG_HPP_ +#define LTTNGPY__CONFIG_HPP_ + +#cmakedefine LTTNGPY_DISABLED @LTTNGPY_DISABLED@ +#define LTTNG_CTL_VERSION "@LTTNG_CTL_VERSION@" +#define LTTNG_CTL_VERSION_MAJOR (@LTTNG_CTL_VERSION_MAJOR@) +#define LTTNG_CTL_VERSION_MINOR (@LTTNG_CTL_VERSION_MINOR@) +#define LTTNG_CTL_VERSION_PATCH (@LTTNG_CTL_VERSION_PATCH@) + +#endif // LTTNGPY__CONFIG_HPP_ diff --git a/lttngpy/src/lttngpy/context_app.cpp b/lttngpy/src/lttngpy/context_app.cpp new file mode 100644 index 0000000..9e24dc6 --- /dev/null +++ b/lttngpy/src/lttngpy/context_app.cpp @@ -0,0 +1,58 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "lttngpy/context_app.hpp" + +namespace lttngpy +{ + +std::variant get_app_context( + const std::string & app_context_name) +{ + // Extract provider name and type name: $app.PROVIDER:TYPE + std::string::size_type prefix_start = app_context_name.find(app_context_prefix); + if (std::string::npos == prefix_start) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type provider_name_start = prefix_start + app_context_prefix.length(); + + std::string::size_type provider_ctx_sep = app_context_name.find(app_context_provider_ctx_sep); + if (std::string::npos == provider_ctx_sep) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type provider_name_len = provider_ctx_sep - provider_name_start; + if (0 == provider_name_len) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type ctx_name_start = provider_ctx_sep + app_context_provider_ctx_sep.length(); + std::string::size_type ctx_name_len = app_context_name.length() - ctx_name_start; + if (0 == ctx_name_len) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + + std::string provider_name = app_context_name.substr(provider_name_start, provider_name_len); + std::string ctx_name = app_context_name.substr(ctx_name_start, ctx_name_len); + + struct lttng_event_context_app_ctx app_ctx = {}; + app_ctx.provider_name = provider_name; + app_ctx.ctx_name = ctx_name; + return app_ctx; +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/context_app.hpp b/lttngpy/src/lttngpy/context_app.hpp new file mode 100644 index 0000000..9c24e2e --- /dev/null +++ b/lttngpy/src/lttngpy/context_app.hpp @@ -0,0 +1,49 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__CONTEXT_APP_HPP_ +#define LTTNGPY__CONTEXT_APP_HPP_ + +#include +#include + +namespace lttngpy +{ + +constexpr std::string_view app_context_prefix = "$app."; +constexpr std::string_view app_context_provider_ctx_sep = ":"; + +/** + * See struct lttng_event_context.u.app_ctx. + */ +struct lttng_event_context_app_ctx +{ + std::string provider_name; + std::string ctx_name; +}; + +/** + * Get provider name and context name from app context type name. + * + * The expected format is: '$app.PROVIDER:TYPE' (see lttng-add-context(1)). + * + * \return the provider name and context name, else a negative LTTng error code (if the name isn't + * using the expected format) +*/ +std::variant get_app_context( + const std::string & app_context_name); + +} // namespace lttngpy + +#endif // LTTNGPY__CONTEXT_APP_HPP_ diff --git a/lttngpy/src/lttngpy/context_lttng.cpp b/lttngpy/src/lttngpy/context_lttng.cpp new file mode 100644 index 0000000..985973c --- /dev/null +++ b/lttngpy/src/lttngpy/context_lttng.cpp @@ -0,0 +1,93 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "lttngpy/config.hpp" +#include "lttngpy/context_lttng.hpp" + +namespace lttngpy +{ + +/** + * LTTng context name to context type. + * + * The perf counter context types are skipped and have to be handled separately. + * Some other context types are skipped, since they are aliases/equal to another type. + * Finally, the app context type is skipped for now. + * + * For more information, refer to the lttng-ctl API. + */ +std::map context_name_to_context_type = { + {"pid", LTTNG_EVENT_CONTEXT_PID}, + // LTTNG_EVENT_CONTEXT_PERF_COUNTER is equal to LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER + {"procname", LTTNG_EVENT_CONTEXT_PROCNAME}, + {"prio", LTTNG_EVENT_CONTEXT_PRIO}, + {"nice", LTTNG_EVENT_CONTEXT_NICE}, + {"vpid", LTTNG_EVENT_CONTEXT_VPID}, + {"tid", LTTNG_EVENT_CONTEXT_TID}, + {"vtid", LTTNG_EVENT_CONTEXT_VTID}, + {"ppid", LTTNG_EVENT_CONTEXT_PPID}, + {"vppid", LTTNG_EVENT_CONTEXT_VPPID}, + {"pthread_id", LTTNG_EVENT_CONTEXT_PTHREAD_ID}, + {"hostname", LTTNG_EVENT_CONTEXT_HOSTNAME}, + {"ip", LTTNG_EVENT_CONTEXT_IP}, + // LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER is handled separately + // LTTNG_EVENT_CONTEXT_PERF_THREAD_COUNTER is handled separately + // LTTNG_EVENT_CONTEXT_APP_CONTEXT is handled separately + {"interruptible", LTTNG_EVENT_CONTEXT_INTERRUPTIBLE}, + {"preemptible", LTTNG_EVENT_CONTEXT_PREEMPTIBLE}, + {"need_reschedule", LTTNG_EVENT_CONTEXT_NEED_RESCHEDULE}, + {"migratable", LTTNG_EVENT_CONTEXT_MIGRATABLE}, + {"callstack-kernel", LTTNG_EVENT_CONTEXT_CALLSTACK_KERNEL}, + {"callstack-user", LTTNG_EVENT_CONTEXT_CALLSTACK_USER}, + {"cgroup_ns", LTTNG_EVENT_CONTEXT_CGROUP_NS}, + {"ipc_ns", LTTNG_EVENT_CONTEXT_IPC_NS}, + {"mnt_ns", LTTNG_EVENT_CONTEXT_MNT_NS}, + {"net_ns", LTTNG_EVENT_CONTEXT_NET_NS}, + {"pid_ns", LTTNG_EVENT_CONTEXT_PID_NS}, + {"user_ns", LTTNG_EVENT_CONTEXT_USER_NS}, + {"uts_ns", LTTNG_EVENT_CONTEXT_UTS_NS}, + {"uid", LTTNG_EVENT_CONTEXT_UID}, + {"euid", LTTNG_EVENT_CONTEXT_EUID}, + {"suid", LTTNG_EVENT_CONTEXT_SUID}, + {"gid", LTTNG_EVENT_CONTEXT_GID}, + {"egid", LTTNG_EVENT_CONTEXT_EGID}, + {"sgid", LTTNG_EVENT_CONTEXT_SGID}, + {"vuid", LTTNG_EVENT_CONTEXT_VUID}, + {"veuid", LTTNG_EVENT_CONTEXT_VEUID}, + {"vsuid", LTTNG_EVENT_CONTEXT_VSUID}, + {"vgid", LTTNG_EVENT_CONTEXT_VGID}, + {"vegid", LTTNG_EVENT_CONTEXT_VEGID}, + {"vsgid", LTTNG_EVENT_CONTEXT_VSGID}, +#if (LTTNG_CTL_VERSION_MAJOR >= 2) && (LTTNG_CTL_VERSION_MINOR >= 13) + {"time_ns", LTTNG_EVENT_CONTEXT_TIME_NS}, +#endif // (LTTNG_CTL_VERSION_MAJOR >= 2) && (LTTNG_CTL_VERSION_MINOR >= 13) +}; + +std::optional get_lttng_context_type( + const std::string & lttng_context_field_name) +{ + const auto & it = context_name_to_context_type.find(lttng_context_field_name); + if (it == std::end(lttngpy::context_name_to_context_type)) { + return std::nullopt; + } + return it->second; +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/context_lttng.hpp b/lttngpy/src/lttngpy/context_lttng.hpp new file mode 100644 index 0000000..3f81221 --- /dev/null +++ b/lttngpy/src/lttngpy/context_lttng.hpp @@ -0,0 +1,36 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__CONTEXT_LTTNG_HPP_ +#define LTTNGPY__CONTEXT_LTTNG_HPP_ + +#include + +#include +#include + +namespace lttngpy +{ + +/** + * Get LTTng context type from name. + * + * \return the context type, or `std::nullopt` on failure (if the name isn't known) +*/ +std::optional get_lttng_context_type( + const std::string & lttng_context_field_name); + +} // namespace lttngpy + +#endif // LTTNGPY__CONTEXT_LTTNG_HPP_ diff --git a/lttngpy/src/lttngpy/context_perf.cpp b/lttngpy/src/lttngpy/context_perf.cpp new file mode 100644 index 0000000..b4084c3 --- /dev/null +++ b/lttngpy/src/lttngpy/context_perf.cpp @@ -0,0 +1,296 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include + +#include "lttngpy/context_perf.hpp" +#include "lttngpy/utils.hpp" + +namespace lttngpy +{ + +/** + * Leave the name as an empty string here, we'll fill it in automatically afterwards. + * + * The last item is the padding. + */ +#define _CTX(ctx_type, counter_id) lttng_event_perf_counter_ctx{ctx_type, counter_id, "", {}} +#define _CTX_HW(counter_id) _CTX(PERF_TYPE_HARDWARE, counter_id) +#define _CTX_HW_CACHE(cache_id) _CTX(PERF_TYPE_HW_CACHE, cache_id) +#define _CTX_SW(counter_id) _CTX(PERF_TYPE_SOFTWARE, counter_id) +#define CTX_HW(name, counter_id) {name, _CTX_HW(counter_id)} +#define CTX_SW(name, counter_id) {name, _CTX_SW(counter_id)} + +/** + * Get 'config' value for a cache/op/result combination. + * + * See perf_event_open(2) for 'config' equation for type PERF_TYPE_HW_CACHE. + */ +#define _CTX_HW_CACHE_OP_RESULT(cache_id, op_id, result_id) \ + (cache_id) | (op_id << 8) | (result_id << 16) +#define CTX_HW_CACHE_OP_RESULT(name, cache_id, op_id, result_id) \ + {name, _CTX_HW_CACHE(_CTX_HW_CACHE_OP_RESULT(cache_id, op_id, result_id))} + +/** + * Each cache counter has 6 different variants: read/write/prefetch and access/miss (3 * 2 = 6). + */ +#define CTX_HW_CACHE(name, cache_id) \ + CTX_HW_CACHE_OP_RESULT( \ + name "-loads", cache_id, \ + PERF_COUNT_HW_CACHE_OP_READ, PERF_COUNT_HW_CACHE_RESULT_ACCESS), \ + CTX_HW_CACHE_OP_RESULT( \ + name "-load-misses", cache_id, \ + PERF_COUNT_HW_CACHE_OP_READ, PERF_COUNT_HW_CACHE_RESULT_MISS), \ + CTX_HW_CACHE_OP_RESULT( \ + name "-stores", cache_id, \ + PERF_COUNT_HW_CACHE_OP_WRITE, PERF_COUNT_HW_CACHE_RESULT_ACCESS), \ + CTX_HW_CACHE_OP_RESULT( \ + name "-store-misses", cache_id, \ + PERF_COUNT_HW_CACHE_OP_WRITE, PERF_COUNT_HW_CACHE_RESULT_MISS), \ + CTX_HW_CACHE_OP_RESULT( \ + name "-prefetches", cache_id, \ + PERF_COUNT_HW_CACHE_OP_PREFETCH, PERF_COUNT_HW_CACHE_RESULT_ACCESS), \ + CTX_HW_CACHE_OP_RESULT( \ + name "-prefetch-misses", cache_id, \ + PERF_COUNT_HW_CACHE_OP_PREFETCH, PERF_COUNT_HW_CACHE_RESULT_MISS) + +/** + * LTTng perf counter name to perf counter context struct. + * + * For more information, refer to the lttng-ctl API and perf API. + * + * LTTng suppports 'perf:[counter name]' counters as old/backwards-compatible names for + * 'perf:{cpu,thread}:[counter name]', but we leave them out here. + */ +std::map perf_counter_name_to_perf_context = { + // Per-CPU counters: hardware + CTX_HW("perf:cpu:cpu-cycles", PERF_COUNT_HW_CPU_CYCLES), + CTX_HW("perf:cpu:cycles", PERF_COUNT_HW_CPU_CYCLES), // Alias + CTX_HW("perf:cpu:instructions", PERF_COUNT_HW_INSTRUCTIONS), + CTX_HW("perf:cpu:cache-references", PERF_COUNT_HW_CACHE_REFERENCES), + CTX_HW("perf:cpu:cache-misses", PERF_COUNT_HW_CACHE_MISSES), + CTX_HW("perf:cpu:branch-instructions", PERF_COUNT_HW_BRANCH_INSTRUCTIONS), + CTX_HW("perf:cpu:branches", PERF_COUNT_HW_BRANCH_INSTRUCTIONS), // Alias + CTX_HW("perf:cpu:branch-misses", PERF_COUNT_HW_BRANCH_MISSES), + CTX_HW("perf:cpu:bus-cycles", PERF_COUNT_HW_BUS_CYCLES), + CTX_HW("perf:cpu:stalled-cycles-frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND), + CTX_HW("perf:cpu:idle-cycles-frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND), // Alias + CTX_HW("perf:cpu:stalled-cycles-backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND), + CTX_HW("perf:cpu:idle-cycles-backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND), + + // Per-CPU counters: hardware cache + CTX_HW_CACHE("perf:cpu:L1-dcache", PERF_COUNT_HW_CACHE_L1D), + CTX_HW_CACHE("perf:cpu:L1-icache", PERF_COUNT_HW_CACHE_L1I), + CTX_HW_CACHE("perf:cpu:LLC", PERF_COUNT_HW_CACHE_LL), + CTX_HW_CACHE("perf:cpu:dTLB", PERF_COUNT_HW_CACHE_DTLB), + CTX_HW_CACHE_OP_RESULT( + "perf:cpu:iTLB-loads", + PERF_COUNT_HW_CACHE_ITLB, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_ACCESS), + CTX_HW_CACHE_OP_RESULT( + "perf:cpu:iTLB-load-misses", + PERF_COUNT_HW_CACHE_ITLB, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_MISS), + CTX_HW_CACHE_OP_RESULT( + "perf:cpu:branch-loads", + PERF_COUNT_HW_CACHE_BPU, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_ACCESS), + CTX_HW_CACHE_OP_RESULT( + "perf:cpu:branch-load-misses", + PERF_COUNT_HW_CACHE_BPU, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_MISS), + + // Per-CPU counters: software + CTX_SW("perf:cpu:cpu-clock", PERF_COUNT_SW_CPU_CLOCK), + CTX_SW("perf:cpu:task-clock", PERF_COUNT_SW_TASK_CLOCK), + CTX_SW("perf:cpu:page-fault", PERF_COUNT_SW_PAGE_FAULTS), + CTX_SW("perf:cpu:faults", PERF_COUNT_SW_PAGE_FAULTS), // Alias + CTX_SW("perf:cpu:context-switches", PERF_COUNT_SW_CONTEXT_SWITCHES), + CTX_SW("perf:cpu:cs", PERF_COUNT_SW_CONTEXT_SWITCHES), // Alias + CTX_SW("perf:cpu:cpu-migrations", PERF_COUNT_SW_CPU_MIGRATIONS), + CTX_SW("perf:cpu:migrations", PERF_COUNT_SW_CPU_MIGRATIONS), // Alias + CTX_SW("perf:cpu:minor-faults", PERF_COUNT_SW_PAGE_FAULTS_MIN), + CTX_SW("perf:cpu:major-faults", PERF_COUNT_SW_PAGE_FAULTS_MAJ), + CTX_SW("perf:cpu:alignment-faults", PERF_COUNT_SW_ALIGNMENT_FAULTS), + CTX_SW("perf:cpu:emulation-faults", PERF_COUNT_SW_EMULATION_FAULTS), + + // Per-thread counters: hardware + CTX_HW("perf:thread:cpu-cycles", PERF_COUNT_HW_CPU_CYCLES), + CTX_HW("perf:thread:clock", PERF_COUNT_HW_CPU_CYCLES), + CTX_HW("perf:thread:instructions", PERF_COUNT_HW_INSTRUCTIONS), + CTX_HW("perf:thread:cache-references", PERF_COUNT_HW_CACHE_REFERENCES), + CTX_HW("perf:thread:cache-misses", PERF_COUNT_HW_CACHE_MISSES), + CTX_HW("perf:thread:branch-instructions", PERF_COUNT_HW_BRANCH_INSTRUCTIONS), + CTX_HW("perf:thread:branches", PERF_COUNT_HW_BRANCH_INSTRUCTIONS), // Alias + CTX_HW("perf:thread:branch-misses", PERF_COUNT_HW_BRANCH_MISSES), + CTX_HW("perf:thread:bus-cycles", PERF_COUNT_HW_BUS_CYCLES), + CTX_HW("perf:thread:stalled-cycles-frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND), + CTX_HW("perf:thread:idle-cycles-frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND), // Alias + CTX_HW("perf:thread:stalled-cycles-backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND), + CTX_HW("perf:thread:idle-cycles-backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND), // Alias + + // Per-thread counters: hardware cache + CTX_HW_CACHE("perf:thread:L1-dcache", PERF_COUNT_HW_CACHE_L1D), + CTX_HW_CACHE("perf:thread:L1-icache", PERF_COUNT_HW_CACHE_L1I), + CTX_HW_CACHE("perf:thread:LLC", PERF_COUNT_HW_CACHE_LL), + CTX_HW_CACHE("perf:thread:dTLB", PERF_COUNT_HW_CACHE_DTLB), + CTX_HW_CACHE_OP_RESULT( + "perf:thread:iTLB-loads", + PERF_COUNT_HW_CACHE_ITLB, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_ACCESS), + CTX_HW_CACHE_OP_RESULT( + "perf:thread:iTLB-load-misses", + PERF_COUNT_HW_CACHE_ITLB, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_MISS), + CTX_HW_CACHE_OP_RESULT( + "perf:thread:branch-loads", + PERF_COUNT_HW_CACHE_BPU, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_ACCESS), + CTX_HW_CACHE_OP_RESULT( + "perf:thread:branch-load-misses", + PERF_COUNT_HW_CACHE_BPU, + PERF_COUNT_HW_CACHE_OP_READ, + PERF_COUNT_HW_CACHE_RESULT_MISS), + + // Per-thread counters: software + CTX_SW("perf:thread:cpu-clock", PERF_COUNT_SW_CPU_CLOCK), + CTX_SW("perf:thread:task-clock", PERF_COUNT_SW_TASK_CLOCK), + CTX_SW("perf:thread:page-fault", PERF_COUNT_SW_PAGE_FAULTS), + CTX_SW("perf:thread:faults", PERF_COUNT_SW_PAGE_FAULTS), // Alias + CTX_SW("perf:thread:context-switches", PERF_COUNT_SW_CONTEXT_SWITCHES), + CTX_SW("perf:thread:cs", PERF_COUNT_SW_CONTEXT_SWITCHES), // Alias + CTX_SW("perf:thread:cpu-migrations", PERF_COUNT_SW_CPU_MIGRATIONS), + CTX_SW("perf:thread:migrations", PERF_COUNT_SW_CPU_MIGRATIONS), // Alias + CTX_SW("perf:thread:minor-faults", PERF_COUNT_SW_PAGE_FAULTS_MIN), + CTX_SW("perf:thread:major-faults", PERF_COUNT_SW_PAGE_FAULTS_MAJ), + CTX_SW("perf:thread:alignment-faults", PERF_COUNT_SW_ALIGNMENT_FAULTS), + CTX_SW("perf:thread:emulation-faults", PERF_COUNT_SW_EMULATION_FAULTS), +}; + +std::variant _get_perf_raw_counter_context( + const std::string & perf_counter_name, + const bool is_cpu_raw_counter, + const bool is_thread_raw_counter) +{ + struct lttng_event_perf_counter_ctx context = {}; + context.type = lttngpy::PERF_TYPE_RAW; + + // Extract mask (NNN) and name: perf:{cpu,thread}:raw:rNNN:NAME + std::string mask_prefix; + if (is_cpu_raw_counter) { + mask_prefix = + std::string(perf_counter_cpu_prefix) + + std::string(perf_counter_raw_prefix) + + std::string(perf_counter_mask_prefix); + } else if (is_thread_raw_counter) { + mask_prefix = + std::string(perf_counter_thread_prefix) + + std::string(perf_counter_raw_prefix) + + std::string(perf_counter_mask_prefix); + } else { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type prefix_start = perf_counter_name.find(mask_prefix); + if (std::string::npos == prefix_start) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type mask_start = prefix_start + mask_prefix.length(); + + std::string::size_type mask_name_sep = + perf_counter_name.find(perf_counter_mask_name_sep, mask_start); + if (std::string::npos == mask_name_sep) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + std::string::size_type mask_len = mask_name_sep - mask_start; + // Mask should not be empty + if (0 == mask_len) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + + std::string::size_type name_start = mask_name_sep + perf_counter_mask_name_sep.length(); + std::string::size_type name_len = perf_counter_name.length() - name_start; + // Name should not be empty + if (0 == name_len) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + + std::string mask_str = perf_counter_name.substr(mask_start, mask_len); + std::string name = perf_counter_name.substr(name_start, name_len); + // Should not have another ':' in the name, after the sep between mask and name + if (std::string::npos != name.find(':')) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + + // Mask value should be hex (see perf-record(1)) + const auto mask_opt = lttngpy::optional_stoull(mask_str, 16); + if (!mask_opt.has_value()) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + context.config = mask_opt.value(); + name.copy(context.name, LTTNG_SYMBOL_NAME_LEN); + return context; +} + +std::variant _get_perf_hw_sw_counter_context( + const std::string & perf_counter_name) +{ + const auto & it = lttngpy::perf_counter_name_to_perf_context.find(perf_counter_name); + if (it == std::end(lttngpy::perf_counter_name_to_perf_context)) { + return -LTTNG_ERR_UST_CONTEXT_INVAL; + } + + /** + * lttng_event_perf_counter_ctx.name should be the LTTng perf context field name with ':' and '-' + * replaced with '_'. + */ + struct lttng_event_perf_counter_ctx context = it->second; + std::string name(perf_counter_name); + std::replace(std::begin(name), std::end(name), ':', '_'); + std::replace(std::begin(name), std::end(name), '-', '_'); + name.copy(context.name, LTTNG_SYMBOL_NAME_LEN); + return context; +} + +std::variant get_perf_counter_context( + const std::string & perf_counter_name) +{ + // Check if it's a raw counter first + const bool is_cpu_raw_counter = lttngpy::starts_with( + perf_counter_name, + std::string(perf_counter_cpu_prefix) + std::string(perf_counter_raw_prefix)); + const bool is_thread_raw_counter = lttngpy::starts_with( + perf_counter_name, + std::string(perf_counter_thread_prefix) + std::string(perf_counter_raw_prefix)); + if (is_cpu_raw_counter || is_thread_raw_counter) { + return _get_perf_raw_counter_context( + perf_counter_name, is_cpu_raw_counter, is_thread_raw_counter); + } + // Otherwise it's a normal hardware or software counter + return _get_perf_hw_sw_counter_context(perf_counter_name); +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/context_perf.hpp b/lttngpy/src/lttngpy/context_perf.hpp new file mode 100644 index 0000000..a2c0f50 --- /dev/null +++ b/lttngpy/src/lttngpy/context_perf.hpp @@ -0,0 +1,143 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__CONTEXT_PERF_HPP_ +#define LTTNGPY__CONTEXT_PERF_HPP_ + +#include + +#include +#include + +namespace lttngpy +{ + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_type_id +{ + PERF_TYPE_HARDWARE = 0, + PERF_TYPE_SOFTWARE = 1, + PERF_TYPE_TRACEPOINT = 2, // Unused here + PERF_TYPE_HW_CACHE = 3, + PERF_TYPE_RAW = 4, // Unused here + PERF_TYPE_BREAKPOINT = 5, // Unused here + PERF_TYPE_MAX, /* non-ABI */ +}; + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_hw_cache_id +{ + PERF_COUNT_HW_CACHE_L1D = 0, + PERF_COUNT_HW_CACHE_L1I = 1, + PERF_COUNT_HW_CACHE_LL = 2, + PERF_COUNT_HW_CACHE_DTLB = 3, + PERF_COUNT_HW_CACHE_ITLB = 4, + PERF_COUNT_HW_CACHE_BPU = 5, + PERF_COUNT_HW_CACHE_NODE = 6, // Unused here + PERF_COUNT_HW_CACHE_MAX, /* non-ABI */ +}; + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_hw_cache_op_id +{ + PERF_COUNT_HW_CACHE_OP_READ = 0, + PERF_COUNT_HW_CACHE_OP_WRITE = 1, + PERF_COUNT_HW_CACHE_OP_PREFETCH = 2, + PERF_COUNT_HW_CACHE_OP_MAX, /* non-ABI */ +}; + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_hw_cache_op_result_id +{ + PERF_COUNT_HW_CACHE_RESULT_ACCESS = 0, + PERF_COUNT_HW_CACHE_RESULT_MISS = 1, + PERF_COUNT_HW_CACHE_RESULT_MAX, /* non-ABI */ +}; + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_hw_id +{ + PERF_COUNT_HW_CPU_CYCLES = 0, + PERF_COUNT_HW_INSTRUCTIONS = 1, + PERF_COUNT_HW_CACHE_REFERENCES = 2, + PERF_COUNT_HW_CACHE_MISSES = 3, + PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4, + PERF_COUNT_HW_BRANCH_MISSES = 5, + PERF_COUNT_HW_BUS_CYCLES = 6, + PERF_COUNT_HW_STALLED_CYCLES_FRONTEND = 7, + PERF_COUNT_HW_STALLED_CYCLES_BACKEND = 8, + PERF_COUNT_HW_REF_CPU_CYCLES = 9, // Unused + PERF_COUNT_HW_MAX, /* non-ABI */ +}; + +/** + * See perf ABI (include/uapi/linux/perf_event.h). + * + * We/LTTng do not support all of the types here. + */ +enum perf_sw_ids +{ + PERF_COUNT_SW_CPU_CLOCK = 0, + PERF_COUNT_SW_TASK_CLOCK = 1, + PERF_COUNT_SW_PAGE_FAULTS = 2, + PERF_COUNT_SW_CONTEXT_SWITCHES = 3, + PERF_COUNT_SW_CPU_MIGRATIONS = 4, + PERF_COUNT_SW_PAGE_FAULTS_MIN = 5, + PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6, + PERF_COUNT_SW_ALIGNMENT_FAULTS = 7, + PERF_COUNT_SW_EMULATION_FAULTS = 8, + PERF_COUNT_SW_DUMMY = 9, // Unused + PERF_COUNT_SW_BPF_OUTPUT = 10, // Unused + PERF_COUNT_SW_CGROUP_SWITCHES = 11, // Unused + PERF_COUNT_SW_MAX, /* non-ABI */ +}; + +constexpr std::string_view perf_counter_prefix = "perf:"; +constexpr std::string_view perf_counter_cpu_prefix = "perf:cpu:"; +constexpr std::string_view perf_counter_thread_prefix = "perf:thread:"; +constexpr std::string_view perf_counter_raw_prefix = "raw:"; +constexpr std::string_view perf_counter_mask_prefix = "r"; +constexpr std::string_view perf_counter_mask_name_sep = ":"; + +/** + * Get perf counter context from name. + * + * \return the perf counter context, else a negative LTTng error code (if the name isn't known) +*/ +std::variant get_perf_counter_context( + const std::string & perf_counter_name); + +} // namespace lttngpy + +#endif // LTTNGPY__CONTEXT_PERF_HPP_ diff --git a/lttngpy/src/lttngpy/event.cpp b/lttngpy/src/lttngpy/event.cpp new file mode 100644 index 0000000..dac98a5 --- /dev/null +++ b/lttngpy/src/lttngpy/event.cpp @@ -0,0 +1,211 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "lttngpy/context_app.hpp" +#include "lttngpy/context_lttng.hpp" +#include "lttngpy/context_perf.hpp" +#include "lttngpy/event.hpp" +#include "lttngpy/utils.hpp" + +namespace lttngpy +{ + +int enable_events( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const std::string & channel_name, + const std::set & events) +{ + if (events.empty()) { + return 0; + } + + // We do not actually need to specify a buffer type for this + struct lttng_domain domain {}; + domain.type = domain_type; + + struct lttng_handle * handle = lttng_create_handle(session_name.c_str(), &domain); + if (nullptr == handle) { + return -LTTNG_ERR_UNK; + } + + int ret = 0; + for (const auto & event_name : events) { + struct lttng_event * event = lttng_event_create(); + if (nullptr == event) { + ret = -LTTNG_ERR_UNK; + break; + } + event_name.copy(event->name, LTTNG_SYMBOL_NAME_LEN); + event->type = LTTNG_EVENT_TRACEPOINT; + + ret = lttng_enable_event(handle, event, channel_name.c_str()); + lttng_event_destroy(event); + if (0 != ret) { + break; + } + } + + lttng_destroy_handle(handle); + return ret; +} + +std::variant> get_tracepoints(const enum lttng_domain_type domain_type) +{ + // We do not actually need to specify a buffer type for this + struct lttng_domain domain {}; + domain.type = domain_type; + + struct lttng_handle * handle = lttng_create_handle(nullptr, &domain); + if (nullptr == handle) { + // This seems to be what lttng-ctl itself returns in this case + return -LTTNG_ERR_NOMEM; + } + + struct lttng_event * events = nullptr; + int ret = lttng_list_tracepoints(handle, &events); + std::variant> tracepoints_var = ret; + if (0 <= ret) { + std::set tracepoints = {}; + const int num_events = ret; + for (int i = 0; i < num_events; i++) { + tracepoints.insert(events[i].name); + } + tracepoints_var = tracepoints; + } + + std::free(events); + lttng_destroy_handle(handle); + return tracepoints_var; +} + +int _fill_in_event_context( + const std::string & context_field, + const enum lttng_domain_type domain_type, + struct lttng_event_context * context) +{ + // Context type + enum lttng_event_context_type ctx; + if (lttngpy::starts_with(context_field, std::string(lttngpy::app_context_prefix))) { + // App context + ctx = LTTNG_EVENT_CONTEXT_APP_CONTEXT; + const auto app_context_var = lttngpy::get_app_context(context_field); + if (std::holds_alternative(app_context_var)) { + return std::get(app_context_var); + } + const auto app_context = std::get(app_context_var); + // The caller must free the char arrays after use + context->u.app_ctx.provider_name = strdup(app_context.provider_name.c_str()); + context->u.app_ctx.ctx_name = strdup(app_context.ctx_name.c_str()); + } else if (lttngpy::starts_with(context_field, std::string(lttngpy::perf_counter_prefix))) { + // Perf counter context + const bool is_cpu_counter = + lttngpy::starts_with(context_field, std::string(lttngpy::perf_counter_cpu_prefix)); + const bool is_thread_counter = + lttngpy::starts_with(context_field, std::string(lttngpy::perf_counter_thread_prefix)); + if (is_cpu_counter) { + ctx = LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER; + } else if (is_thread_counter) { + ctx = LTTNG_EVENT_CONTEXT_PERF_THREAD_COUNTER; + } else { + return -LTTNG_ERR_UNK; + } + } else { + // Normal LTTng context + const auto context_type_opt = lttngpy::get_lttng_context_type(context_field); + if (!context_type_opt.has_value()) { + switch (domain_type) { + case LTTNG_DOMAIN_KERNEL: + return -LTTNG_ERR_KERN_CONTEXT_FAIL; + case LTTNG_DOMAIN_UST: + return -LTTNG_ERR_UST_CONTEXT_INVAL; + case LTTNG_DOMAIN_NONE: // Fall through + case LTTNG_DOMAIN_JUL: // Fall through + case LTTNG_DOMAIN_LOG4J: // Fall through + case LTTNG_DOMAIN_PYTHON: // Fall through + default: + return -LTTNG_ERR_UNK; + } + } + ctx = context_type_opt.value(); + } + context->ctx = ctx; + + // Perf counters + if (context->ctx == LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER || + context->ctx == LTTNG_EVENT_CONTEXT_PERF_THREAD_COUNTER) + { + const auto counter_context_type_var = lttngpy::get_perf_counter_context(context_field); + if (std::holds_alternative(counter_context_type_var)) { + return std::get(counter_context_type_var); + } + context->u.perf_counter = std::get(counter_context_type_var); + } + return 0; +} + +int add_contexts( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const std::string & channel_name, + const std::set & context_fields) +{ + if (context_fields.empty()) { + return 0; + } + + // We do not actually need to specify a buffer type for this + struct lttng_domain domain {}; + domain.type = domain_type; + + struct lttng_handle * handle = lttng_create_handle(session_name.c_str(), &domain); + if (nullptr == handle) { + return -LTTNG_ERR_UNK; + } + + int ret = 0; + for (const auto & context_field : context_fields) { + struct lttng_event_context context = {}; + ret = _fill_in_event_context(context_field, domain_type, &context); + if (0 == ret) { + ret = lttng_add_context(handle, &context, nullptr, channel_name.c_str()); + } + + // Free app context strings + if (LTTNG_EVENT_CONTEXT_APP_CONTEXT == context.ctx) { + std::free(context.u.app_ctx.provider_name); + std::free(context.u.app_ctx.ctx_name); + } + + if (0 != ret) { + break; + } + } + + lttng_destroy_handle(handle); + return ret; +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/event.hpp b/lttngpy/src/lttngpy/event.hpp new file mode 100644 index 0000000..82e7f10 --- /dev/null +++ b/lttngpy/src/lttngpy/event.hpp @@ -0,0 +1,69 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__EVENT_HPP_ +#define LTTNGPY__EVENT_HPP_ + +#include +#include + +#include +#include +#include + +namespace lttngpy +{ + +/** + * Enable events. + * + * \param session_name the session name + * \param domain_type the domain type + * \param channel_name the channel name + * \param events the set of event names + * \return 0 on success, else a negative LTTng error code + */ +int enable_events( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const std::string & channel_name, + const std::set & events); + +/** + * Get tracepoints. + * + * \param domain_type the domain type + * \return the set of tracepoints, else a negative LTTng error code (e.g., if kernel tracer is not + * available when providing the kernel domain) + */ +std::variant> get_tracepoints(const enum lttng_domain_type domain_type); + +/** + * Add contexts. + * + * \param session_name the session name + * \param domain_type the domain type + * \param channel_name the channel name + * \param context_fields the set of context field names + * \return 0 on success, else a negative LTTng error code + */ +int add_contexts( + const std::string & session_name, + const enum lttng_domain_type domain_type, + const std::string & channel_name, + const std::set & context_fields); + +} // namespace lttngpy + +#endif // LTTNGPY__EVENT_HPP_ diff --git a/lttngpy/src/lttngpy/lttng.cpp b/lttngpy/src/lttngpy/lttng.cpp new file mode 100644 index 0000000..628e5c3 --- /dev/null +++ b/lttngpy/src/lttngpy/lttng.cpp @@ -0,0 +1,27 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "lttngpy/lttng.hpp" + +namespace lttngpy +{ + +bool is_lttng_session_daemon_alive() +{ + return 1 == lttng_session_daemon_alive(); +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/lttng.hpp b/lttngpy/src/lttngpy/lttng.hpp new file mode 100644 index 0000000..d87cf15 --- /dev/null +++ b/lttngpy/src/lttngpy/lttng.hpp @@ -0,0 +1,32 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__LTTNG_HPP_ +#define LTTNGPY__LTTNG_HPP_ + +#include + +namespace lttngpy +{ + +/** + * Check if session daemon is alive. + * + * \return true if alive, false otherwise + */ +bool is_lttng_session_daemon_alive(); + +} // namespace lttngpy + +#endif // LTTNGPY__LTTNG_HPP_ diff --git a/lttngpy/src/lttngpy/session.cpp b/lttngpy/src/lttngpy/session.cpp new file mode 100644 index 0000000..8fd989c --- /dev/null +++ b/lttngpy/src/lttngpy/session.cpp @@ -0,0 +1,67 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include + +#include "lttngpy/session.hpp" + +namespace lttngpy +{ + +std::variant> get_session_names() +{ + struct lttng_session * sessions = nullptr; + int ret = lttng_list_sessions(&sessions); + if (0 > ret) { + std::free(sessions); + return ret; + } + + std::set session_names = {}; + const int num_sessions = ret; + for (int i = 0; i < num_sessions; i++) { + session_names.insert(sessions[i].name); + } + std::free(sessions); + return session_names; +} + +int destroy_all_sessions() +{ + const auto & session_names_opt = lttngpy::get_session_names(); + if (std::holds_alternative(session_names_opt)) { + return std::get(session_names_opt); + } + + int overall_ret = 0; + const auto session_names = std::get>(session_names_opt); + for (const auto & session_name : session_names) { + /** + * Try to destroy all sessions, but return the last error code if any session destruction was + * unsuccessful. + */ + int ret = lttng_destroy_session(session_name.c_str()); + if (0 != ret) { + overall_ret = ret; + } + } + return overall_ret; +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/session.hpp b/lttngpy/src/lttngpy/session.hpp new file mode 100644 index 0000000..e8806c4 --- /dev/null +++ b/lttngpy/src/lttngpy/session.hpp @@ -0,0 +1,43 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__SESSION_HPP_ +#define LTTNGPY__SESSION_HPP_ + +#include +#include +#include + +namespace lttngpy +{ + +/** + * Get the currently-existing session names. + * + * \return the set of session names, else a negative LTTng error code + */ +std::variant> get_session_names(); + +/** + * Destroy all sessions. + * + * Tries to destroy all sessions, and reports an error if any session destruction was unsuccessful. + * + * \return 0 on success, else a negative LTTng error code + */ +int destroy_all_sessions(); + +} // namespace lttngpy + +#endif // LTTNGPY__SESSION_HPP_ diff --git a/lttngpy/src/lttngpy/status.cpp b/lttngpy/src/lttngpy/status.cpp new file mode 100644 index 0000000..9127033 --- /dev/null +++ b/lttngpy/src/lttngpy/status.cpp @@ -0,0 +1,30 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "lttngpy/config.hpp" +#include "lttngpy/status.hpp" + +namespace lttngpy +{ + +bool is_available() +{ +#ifndef LTTNGPY_DISABLED + return true; +#else + return false; +#endif +} + +} // namespace lttngpy diff --git a/lttngpy/src/lttngpy/status.hpp b/lttngpy/src/lttngpy/status.hpp new file mode 100644 index 0000000..d2c8ca7 --- /dev/null +++ b/lttngpy/src/lttngpy/status.hpp @@ -0,0 +1,35 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__STATUS_HPP_ +#define LTTNGPY__STATUS_HPP_ + +namespace lttngpy +{ + +/** + * Check if lttng-ctl is available. + * + * This is the only function guaranteed to exist in this Python module. If this returns false, then + * it means that no other functions are available. + * + * This is false on non-Linux platforms, or if it was explicitly disabled during build. + * + * \return true if available, false otherwise + */ +bool is_available(); + +} // namespace lttngpy + +#endif // LTTNGPY__STATUS_HPP_ diff --git a/lttngpy/src/lttngpy/utils.hpp b/lttngpy/src/lttngpy/utils.hpp new file mode 100644 index 0000000..07f6643 --- /dev/null +++ b/lttngpy/src/lttngpy/utils.hpp @@ -0,0 +1,47 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LTTNGPY__UTILS_HPP_ +#define LTTNGPY__UTILS_HPP_ + +#include +#include +#include + +namespace lttngpy +{ + +inline bool starts_with(const std::string & str, const std::string & substr) +{ + return std::equal(substr.begin(), substr.end(), str.begin()); +} + +inline std::optional optional_stoull(const std::string & str, int base) +{ + try { + return static_cast(std::stoull(str, nullptr, base)); + } catch (...) { + } + return std::nullopt; +} + +template +inline bool is_int(const std::variant & var) +{ + return std::holds_alternative(var); +} + +} // namespace lttngpy + +#endif // LTTNGPY__UTILS_HPP_ diff --git a/lttngpy/test/test_constants.py b/lttngpy/test/test_constants.py new file mode 100644 index 0000000..7fe5616 --- /dev/null +++ b/lttngpy/test/test_constants.py @@ -0,0 +1,29 @@ +# Copyright 2023 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from lttngpy import impl as lttngpy + + +class TestConstants(unittest.TestCase): + + def test_version(self): + self.assertRegex(lttngpy.LTTNG_CTL_VERSION, r'^\d+\.\d+\.\d+$') + + def test_error(self): + # Just check a couple to make sure they're present + self.assertEqual(10, lttngpy.LTTNG_OK) + self.assertTrue(0 < len(lttngpy.lttng_strerror(lttngpy.LTTNG_OK))) + self.assertEqual(11, lttngpy.LTTNG_ERR_UNK) diff --git a/lttngpy/test/test_context_app.cpp b/lttngpy/test/test_context_app.cpp new file mode 100644 index 0000000..df314fc --- /dev/null +++ b/lttngpy/test/test_context_app.cpp @@ -0,0 +1,38 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "lttngpy/context_app.hpp" +#include "lttngpy/utils.hpp" + +TEST(TestContextApp, invalid_app_context_name) { + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context(""))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context("$app."))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context(":"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context("$app.p"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context("$app.p:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context("$app.:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_app_context("$app.:c"))); +} + +TEST(TestContextApp, valid_app_context_name) { + const auto var = lttngpy::get_app_context("$app.provider:type"); + ASSERT_TRUE(!std::holds_alternative(var)); + const auto context = std::get(var); + EXPECT_EQ("provider", context.provider_name); + EXPECT_EQ("type", context.ctx_name); +} diff --git a/lttngpy/test/test_context_lttng.cpp b/lttngpy/test/test_context_lttng.cpp new file mode 100644 index 0000000..c843398 --- /dev/null +++ b/lttngpy/test/test_context_lttng.cpp @@ -0,0 +1,31 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "lttngpy/context_lttng.hpp" + +TEST(TestContextLttng, invalid_context_name) { + EXPECT_EQ(std::nullopt, lttngpy::get_lttng_context_type("")); + EXPECT_EQ(std::nullopt, lttngpy::get_lttng_context_type("unknown")); +} + +TEST(TestContextLttng, valid_context_name) { + const auto opt = lttngpy::get_lttng_context_type("pid"); + ASSERT_TRUE(opt.has_value()); + EXPECT_EQ(LTTNG_EVENT_CONTEXT_PID, opt.value()); +} diff --git a/lttngpy/test/test_context_perf.cpp b/lttngpy/test/test_context_perf.cpp new file mode 100644 index 0000000..3d7db6e --- /dev/null +++ b/lttngpy/test/test_context_perf.cpp @@ -0,0 +1,70 @@ +// Copyright 2023 Apex.AI, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "lttngpy/context_perf.hpp" +#include "lttngpy/utils.hpp" + +TEST(TestContextPerf, invalid_context_name) { + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context(""))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("unknown"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:cpu"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:cpu:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:cpu:unknown"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:unknown"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw::"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:r"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:r:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:r1:"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:r:name"))); + EXPECT_TRUE(lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:b1234:name"))); + EXPECT_TRUE( + lttngpy::is_int(lttngpy::get_perf_counter_context("perf:thread:raw:r0110:name:extra"))); +} + +TEST(TestContextPerf, valid_context_name) { + auto var = lttngpy::get_perf_counter_context("perf:thread:task-clock"); + ASSERT_TRUE(!std::holds_alternative(var)); + auto context = std::get(var); + EXPECT_EQ(lttngpy::PERF_TYPE_SOFTWARE, context.type); + EXPECT_EQ(lttngpy::PERF_COUNT_SW_TASK_CLOCK, context.config); + EXPECT_STREQ("perf_thread_task_clock", context.name); + + var = lttngpy::get_perf_counter_context("perf:cpu:dTLB-loads"); + ASSERT_TRUE(!std::holds_alternative(var)); + context = std::get(var); + EXPECT_EQ(lttngpy::PERF_TYPE_HW_CACHE, context.type); + // See equation + EXPECT_EQ( + (lttngpy::PERF_COUNT_HW_CACHE_DTLB) | + (lttngpy::PERF_COUNT_HW_CACHE_OP_READ << 8) | + (lttngpy::PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16), + context.config); + EXPECT_STREQ("perf_cpu_dTLB_loads", context.name); + + var = lttngpy::get_perf_counter_context("perf:thread:raw:r0110:name"); + ASSERT_TRUE(!std::holds_alternative(var)); + context = std::get(var); + EXPECT_EQ(lttngpy::PERF_TYPE_RAW, context.type); + EXPECT_EQ(272, context.config); + EXPECT_STREQ("name", context.name); +} diff --git a/lttngpy/test/test_session.py b/lttngpy/test/test_session.py new file mode 100644 index 0000000..c7732f4 --- /dev/null +++ b/lttngpy/test/test_session.py @@ -0,0 +1,68 @@ +# Copyright 2023 Apex.AI, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import shutil +import tempfile +import unittest + +from lttngpy import impl as lttngpy + + +@unittest.skipIf(not lttngpy.is_lttng_session_daemon_alive(), 'session daemon required') +class TestSession(unittest.TestCase): + + def create_test_tmpdir(self, test_name: str) -> str: + prefix = self.__class__.__name__ + '__' + test_name + return tempfile.mkdtemp(prefix=prefix) + + def test_session_list_create_start_stop_destroy(self): + session_name = 'test_session_list_create_start_stop_destroy' + tmpdir = self.create_test_tmpdir(session_name) + + self.assertSetEqual(set(), lttngpy.get_session_names()) + self.assertEqual(0, lttngpy.lttng_create_session(session_name=session_name, url=tmpdir)) + self.assertSetEqual({session_name}, lttngpy.get_session_names()) + self.assertEqual( + 0, + lttngpy.enable_channel( + session_name=session_name, + domain_type=lttngpy.LTTNG_DOMAIN_UST, + buffer_type=lttngpy.LTTNG_BUFFER_PER_UID, + channel_name='dummy_channel', + overwrite=None, + subbuf_size=None, + num_subbuf=None, + switch_timer_interval=None, + read_timer_interval=None, + output=None, + ), + ) + self.assertEqual(0, lttngpy.lttng_start_tracing(session_name=session_name)) + self.assertEqual(0, lttngpy.lttng_stop_tracing(session_name=session_name)) + self.assertEqual(0, lttngpy.lttng_destroy_session(session_name=session_name)) + self.assertSetEqual(set(), lttngpy.get_session_names()) + + self.assertEqual(0, lttngpy.lttng_create_session(session_name=session_name, url=tmpdir)) + self.assertEqual(0, lttngpy.destroy_all_sessions()) + self.assertSetEqual(set(), lttngpy.get_session_names()) + + shutil.rmtree(tmpdir) + + def test_error(self): + session_name = 'test_error' + self.assertSetEqual(set(), lttngpy.get_session_names()) + self.assertNotEqual(0, lttngpy.lttng_start_tracing(session_name=session_name)) + self.assertNotEqual(0, lttngpy.lttng_stop_tracing(session_name=session_name)) + self.assertNotEqual(0, lttngpy.lttng_destroy_session(session_name=session_name)) + self.assertSetEqual(set(), lttngpy.get_session_names()) diff --git a/test_ros2trace/package.xml b/test_ros2trace/package.xml index 73125ed..7757b61 100644 --- a/test_ros2trace/package.xml +++ b/test_ros2trace/package.xml @@ -18,6 +18,7 @@ ament_xmllint launch launch_ros + lttngpy python3-pytest ros2run ros2trace diff --git a/test_ros2trace/test/test_ros2trace/test_trace.py b/test_ros2trace/test/test_ros2trace/test_trace.py index 43a3e9a..d2e3f71 100644 --- a/test_ros2trace/test/test_ros2trace/test_trace.py +++ b/test_ros2trace/test/test_ros2trace/test_trace.py @@ -25,6 +25,7 @@ from launch import LaunchDescription from launch import LaunchService from launch_ros.actions import Node +from lttngpy import impl as lttngpy from tracetools_trace.tools import tracepoints from tracetools_trace.tools.lttng import is_lttng_installed @@ -63,30 +64,26 @@ def tearDown(self) -> None: # Even if running 'ros2 trace' fails, we do not want any lingering tracing session self.assertNoTracingSession() - def get_tracing_sessions(self) -> Tuple[bool, str]: - output = self.run_lttng_list() - # If there is no session daemon, then there are no tracing sessions - no_session_daemon_available = 'No session daemon is available' in output - if no_session_daemon_available: - return False, output - # Starting from LTTng 2.13, 'tracing session' was replaced with 'recording session' - # (see lttng-tools e971184) - has_tracing_sessions = not any( - f'Currently no available {name} session' in output for name in ('tracing', 'recording') - ) - return has_tracing_sessions, output - def assertTracingSession(self) -> None: - has_tracing_sessions, output = self.get_tracing_sessions() - self.assertTrue(has_tracing_sessions, 'no tracing sessions exist:\n' + output) + self.assertTrue( + lttngpy.is_lttng_session_daemon_alive(), + 'no tracing sessions exist because there is no daemon', + ) + session_names = lttngpy.get_session_names() + has_tracing_sessions = session_names is not None and 0 < len(session_names) + self.assertTrue(has_tracing_sessions, 'no tracing sessions exist') def assertNoTracingSession(self) -> None: - has_tracing_sessions, output = self.get_tracing_sessions() - if has_tracing_sessions: + # If there is no session daemon, then there are no tracing sessions + if not lttngpy.is_lttng_session_daemon_alive(): + return + session_names = lttngpy.get_session_names() + no_tracing_sessions = 0 == len(session_names) + if not no_tracing_sessions: # Destroy tracing sessions if there are any, this way we can continue running tests and # avoid possible interference between them - self.run_lttng_destroy_all() - self.assertFalse(has_tracing_sessions, 'tracing session(s) exist:\n' + output) + self.assertEqual(0, lttngpy.destroy_all_sessions()) + self.assertTrue(no_tracing_sessions, f'tracing session(s) exist: {session_names}') def assertTraceExist(self, trace_dir: str) -> None: self.assertTrue(os.path.isdir(trace_dir), f'trace directory does not exist: {trace_dir}') @@ -116,25 +113,6 @@ def create_test_tmpdir(self, test_name: str) -> str: def get_subdirectories(self, directory: str) -> List[str]: return [f.name for f in os.scandir(directory) if f.is_dir()] - def run_lttng_list(self) -> str: - process = subprocess.run( - ['lttng', 'list'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf-8', - ) - return process.stdout + process.stderr - - def run_lttng_destroy_all(self): - process = subprocess.run( - ['lttng', 'destroy', '--all'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf-8', - ) - output = process.stdout + process.stderr - self.assertEqual(0, process.returncode, f"'lttng destroy' command failed: {output}") - def run_command( self, args: List[str], @@ -370,6 +348,17 @@ def test_base_path_not_exist(self) -> None: shutil.rmtree(tmpdir) + def test_no_events(self) -> None: + tmpdir = self.create_test_tmpdir('test_no_events') + + # Enabling no events should result in an error + ret = self.run_trace_command( + ['--path', tmpdir, '--ust', '--kernel'], + ) + self.assertEqual(1, ret) + + shutil.rmtree(tmpdir) + def test_unknown_context_field(self) -> None: tmpdir = self.create_test_tmpdir('test_unknown_context_field') diff --git a/tracetools_trace/package.xml b/tracetools_trace/package.xml index c7e02d6..53b55e5 100644 --- a/tracetools_trace/package.xml +++ b/tracetools_trace/package.xml @@ -12,8 +12,7 @@ https://github.com/ros2/ros2_tracing/issues Christophe Bedard - lttng-tools - python3-lttng + lttngpy ament_copyright ament_flake8 diff --git a/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py b/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py index 63992e3..312fd79 100644 --- a/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py +++ b/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py @@ -13,7 +13,6 @@ # limitations under the License. import os -import subprocess import tempfile import unittest from unittest import mock @@ -35,32 +34,7 @@ def test_is_lttng_installed(self): with mock.patch('platform.system', return_value='Windows'): self.assertFalse(is_lttng_installed()) - # LTTng command not found - class PopenFileNotFound: - - def __init__(self, *args, **kwargs): - raise FileNotFoundError('file not found') - - with mock.patch.object(subprocess, 'Popen', PopenFileNotFound): - self.assertFalse(is_lttng_installed()) - - # Other error when running LTTng command - class PopenReturnCodeError: - - def __init__(self, *args, **kwargs): - pass - - def communicate(self): - return 'stdout'.encode(), 'stderr'.encode() - - @property - def returncode(self): - return 1 - - with mock.patch.object(subprocess, 'Popen', PopenReturnCodeError): - self.assertFalse(is_lttng_installed()) - - # lttng Python package or version not found + # lttng-ctl or version not found with mock.patch('tracetools_trace.tools.lttng.get_lttng_version', return_value=None): self.assertFalse(is_lttng_installed()) @@ -81,11 +55,18 @@ def test_lttng_not_installed(self): def test_no_kernel_tracer(self): from tracetools_trace.tools.lttng_impl import setup with ( + mock.patch( + 'tracetools_trace.tools.lttng_impl.is_session_daemon_not_alive', + return_value=False, + ), + mock.patch( + 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable', + return_value=False, + ), mock.patch( 'tracetools_trace.tools.lttng_impl.is_kernel_tracer_available', - return_value=(False, 'some error message'), + return_value=False, ), - mock.patch('lttng.session_daemon_alive', return_value=1), ): with self.assertRaises(RuntimeError): setup( @@ -166,9 +147,15 @@ def test_is_session_daemon_unreachable(self): def test_unreachable_session_daemon(self): from tracetools_trace.tools.lttng_impl import setup - with mock.patch( - 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable', - return_value=True, + with ( + mock.patch( + 'tracetools_trace.tools.lttng_impl.is_session_daemon_not_alive', + return_value=False, + ), + mock.patch( + 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable', + return_value=True, + ), ): with self.assertRaises(RuntimeError): setup(session_name='test-session', base_path='/tmp') diff --git a/tracetools_trace/tracetools_trace/tools/lttng.py b/tracetools_trace/tracetools_trace/tools/lttng.py index 8e109ce..cd67aff 100644 --- a/tracetools_trace/tracetools_trace/tools/lttng.py +++ b/tracetools_trace/tracetools_trace/tools/lttng.py @@ -16,42 +16,30 @@ """Interface for tracing with LTTng.""" import platform -import subprocess import sys from typing import Optional from packaging.version import Version try: - from . import lttng_impl - _lttng = lttng_impl # type: ignore + from . import lttng_impl as _lttng except ImportError: # Fall back on stub functions so that this still passes linter checks - from . import lttng_stub - _lttng = lttng_stub # type: ignore + # This will happen if lttngpy isn't found, in which case importing lttng_impl will fail + from . import lttng_stub as _lttng # type: ignore def get_lttng_version() -> Optional[Version]: """ - Get version of lttng Python package. + Get version of lttng-ctl. - :return: the version of the lttng Python package, or `None` if it is not available + :return: the version of lttng-ctl, or `None` if it is not available """ if not hasattr(_lttng, 'get_version') or not callable(_lttng.get_version): return None return _lttng.get_version() -# Check lttng module version -current_version = get_lttng_version() -LTTNG_MIN_VERSION = '2.10.7' -if current_version is None or current_version < Version(LTTNG_MIN_VERSION): - print( - f'lttng module version >={LTTNG_MIN_VERSION} required, found {str(current_version)}', - file=sys.stderr, - ) - - def lttng_init(**kwargs) -> Optional[str]: """ Set up and start LTTng session. @@ -114,14 +102,13 @@ def is_lttng_installed( Check if LTTng is installed. It first checks if the OS can support LTTng. - If so, it then simply checks if LTTng is installed using the 'lttng' command, and checks if the - lttng Python package is installed (python3-lttng). + If so, it then checks if lttng-ctl is installed. - Optionally, a minimum version can also be specified for the lttng Python package. + Optionally, a minimum version can also be specified for lttng-ctl. - :param minimum_version: the minimum required lttng Python package version - :return: True if LTTng and the lttng Python package are installed, and optionally if the - version of lttng Python package is sufficient, False otherwise + :param minimum_version: the minimum required lttng-ctl version + :return: True if lttng-ctl is installed, and optionally if the version of lttng-ctl is + sufficient, False otherwise """ # Check system message_doc = ( @@ -132,32 +119,19 @@ def is_lttng_installed( if 'Linux' != system: print(f"System '{system}' does not support LTTng.\n{message_doc}", file=sys.stderr) return False - # Check if LTTng (CLI) is installed - try: - process = subprocess.Popen( - ['lttng', '--version'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - _, stderr = process.communicate() - if 0 != process.returncode: - raise RuntimeError(stderr.decode()) - except (RuntimeError, FileNotFoundError) as e: - print(f'LTTng not found: {e}\n{message_doc}', file=sys.stderr) - return False - # Check if lttng Python package is installed + # Check if lttng-ctl is installed lttng_version = get_lttng_version() if not lttng_version: print( - f'lttng Python package (python3-lttng) not installed\n{message_doc}', + f'lttng-ctl (liblttng-ctl-dev) not installed\n{message_doc}', file=sys.stderr, ) return False - # Check if lttng Python package version is sufficient + # Check if lttng-ctl version is sufficient if minimum_version and lttng_version < Version(minimum_version): print( ( - f'lttng Python package (python3-lttng) version >={minimum_version} required, ' + f'lttng-ctl (liblttng-ctl-dev) version >={minimum_version} required, ' f'found {str(lttng_version)}' ), file=sys.stderr, diff --git a/tracetools_trace/tracetools_trace/tools/lttng_impl.py b/tracetools_trace/tracetools_trace/tools/lttng_impl.py index 6b8377b..9d5e38b 100644 --- a/tracetools_trace/tracetools_trace/tools/lttng_impl.py +++ b/tracetools_trace/tracetools_trace/tools/lttng_impl.py @@ -16,60 +16,43 @@ """Implementation of the interface for tracing with LTTng.""" import os -import re import shlex import subprocess from typing import Dict from typing import List from typing import Optional from typing import Set -from typing import Tuple from typing import Union -import lttng +from lttngpy import impl as lttngpy from packaging.version import Version -from .names import CONTEXT_TYPE_CONSTANTS_MAP from .names import DEFAULT_CONTEXT from .names import DEFAULT_EVENTS_ROS +from .names import DOMAIN_TYPE_KERNEL +from .names import DOMAIN_TYPE_USERSPACE def get_version() -> Optional[Version]: """ - Get the version of the lttng module. - - The module does not have a __version__ attribute, but the version is mentioned in its __doc__, - and seems to be written in a consistent way across versions. + Get version of lttng-ctl. :return: the version as a Version object, or `None` if it cannot be extracted """ - doc_lines = str(lttng.__doc__).split('\n') - filtered_doc_lines: List[str] = list(filter(None, doc_lines)) - if len(filtered_doc_lines) == 0: - return None - first_line = filtered_doc_lines[0] - version_string = first_line.split(' ')[1] - if not re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$').match(version_string): + if not lttngpy.is_available(): return None - return Version(version_string) + return Version(lttngpy.LTTNG_CTL_VERSION) -def is_kernel_tracer_available() -> Tuple[bool, Optional[str]]: +def is_kernel_tracer_available() -> bool: """ Check if the kernel tracer is available. - Runs 'lttng list -k', which gives an error if the kernel tracer is not available. + This must not be called if `lttngpy.is_available()` is `False`. - :return: (`True` if available or `False` if not, stderr output if unavailable) + :return: `True` if available or `False` if not """ - process = subprocess.run( - ['lttng', 'list', '-k'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if 0 != process.returncode: - return False, process.stderr.decode().strip('\n') - return True, None + return not isinstance(lttngpy.get_tracepoints(domain_type=lttngpy.LTTNG_DOMAIN_KERNEL), int) def get_lttng_home() -> Optional[str]: @@ -137,6 +120,17 @@ def is_session_daemon_unreachable() -> bool: return 1 == process.returncode or 'lttng-sessiond' != process.stdout.strip() +def is_session_daemon_not_alive() -> bool: + """ + Check if the session daemon isn't alive. + + This must not be called if `lttngpy.is_available()` is `False`. + + :return: `True` if the session daemon is not alive, or `False` if it is alive + """ + return not lttngpy.is_lttng_session_daemon_alive() + + def setup( *, session_name: str, @@ -158,6 +152,7 @@ def setup( Initialization will fail if the list of kernel events to be enabled is not empty and if the kernel tracer is not installed. + This must not be called if `lttngpy.is_available()` is `False`. Raises RuntimeError on failure, in which case the tracing session might still exist. :param session_name: the name of the session @@ -189,7 +184,7 @@ def setup( f'trace directory already exists, use the append option to append to it: {full_path}') # If there is no session daemon running, try to spawn one - if lttng.session_daemon_alive() == 0: + if is_session_daemon_not_alive(): subprocess.run( ['lttng-sessiond', '--daemonize'], ) @@ -202,22 +197,20 @@ def setup( 'directory exists. See: https://bugs.lttng.org/issues/1371' ) # Error out if there is still no session daemon - if lttng.session_daemon_alive() == 0: + if is_session_daemon_not_alive(): raise RuntimeError('failed to start lttng session daemon') # Make sure the kernel tracer is available if there are kernel events # Do this after spawning a session daemon, otherwise we can't detect the kernel tracer - if 0 < len(kernel_events): - kernel_tracer_available, message = is_kernel_tracer_available() - if not kernel_tracer_available: - raise RuntimeError( - f'kernel tracer is not available: {message}\n' - ' cannot use kernel events:\n' - " 'ros2 trace' command: cannot use '-k' option\n" - " 'Trace' action: cannot set 'events_kernel'/'events-kernel' list\n" - ' install the kernel tracer, e.g., on Ubuntu, install lttng-modules-dkms\n' - ' see: https://github.com/ros2/ros2_tracing#building' - ) + if 0 < len(kernel_events) and not is_kernel_tracer_available(): + raise RuntimeError( + 'kernel tracer is not available:\n' + ' cannot use kernel events:\n' + " 'ros2 trace' command: cannot use '-k' option\n" + " 'Trace' action: cannot set 'events_kernel'/'events-kernel' list\n" + ' install the kernel tracer, e.g., on Ubuntu, install lttng-modules-dkms\n' + ' see: https://github.com/ros2/ros2_tracing#building' + ) # Convert lists to sets if not isinstance(ros_events, set): @@ -229,65 +222,86 @@ def setup( ust_enabled = ros_events is not None and len(ros_events) > 0 kernel_enabled = kernel_events is not None and len(kernel_events) > 0 - - # Domains - if ust_enabled: - domain_ust = lttng.Domain() - domain_ust.type = lttng.DOMAIN_UST - # Per-user buffer - domain_ust.buf_type = lttng.BUFFER_PER_UID - channel_ust = lttng.Channel() - channel_ust.name = channel_name_ust - # Discard, do not overwrite - channel_ust.attr.overwrite = 0 - # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode, - # and switching between sub-buffers introduces noticeable CPU overhead - channel_ust.attr.subbuf_size = subbuffer_size_ust - channel_ust.attr.num_subbuf = 2 - # Ignore switch timer interval and use read timer instead - channel_ust.attr.switch_timer_interval = 0 - channel_ust.attr.read_timer_interval = 200 - # mmap channel output (only option for UST) - channel_ust.attr.output = lttng.EVENT_MMAP - events_list_ust = _create_events(ros_events) - if kernel_enabled: - domain_kernel = lttng.Domain() - domain_kernel.type = lttng.DOMAIN_KERNEL - # Global buffer (only option for kernel domain) - domain_kernel.buf_type = lttng.BUFFER_GLOBAL - channel_kernel = lttng.Channel() - channel_kernel.name = channel_name_kernel - # Discard, do not overwrite - channel_kernel.attr.overwrite = 0 - channel_kernel.attr.subbuf_size = subbuffer_size_kernel - channel_kernel.attr.num_subbuf = 2 - # Ignore switch timer interval and use read timer instead - channel_kernel.attr.switch_timer_interval = 0 - channel_kernel.attr.read_timer_interval = 200 - # mmap channel output instead of splice - channel_kernel.attr.output = lttng.EVENT_MMAP - events_list_kernel = _create_events(kernel_events) + if not(ust_enabled or kernel_enabled): + raise RuntimeError('no events enabled') # Create session # LTTng will create the parent directories if needed - _create_session(session_name, full_path) + _create_session( + session_name=session_name, + full_path=full_path, + ) - # Handles, channels, events - handle_ust = None + # Enable channel, events, and contexts for each domain + contexts_dict = _normalize_contexts_dict(context_fields) if ust_enabled: - handle_ust = _create_handle(session_name, domain_ust) - _enable_channel(handle_ust, channel_ust) - _enable_events(handle_ust, events_list_ust, channel_ust.name) - handle_kernel = None + domain = DOMAIN_TYPE_USERSPACE + domain_type = lttngpy.LTTNG_DOMAIN_UST + channel_name = channel_name_ust + _enable_channel( + session_name=session_name, + domain_type=domain_type, + # Per-user buffer + buffer_type=lttngpy.LTTNG_BUFFER_PER_UID, + channel_name=channel_name, + # Discard, do not overwrite + overwrite=0, + # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode, + # and switching between sub-buffers introduces noticeable CPU overhead + subbuf_size=subbuffer_size_ust, + num_subbuf=2, + # Ignore switch timer interval and use read timer instead + switch_timer_interval=0, + read_timer_interval=200, + # mmap channel output (only option for UST) + output=lttngpy.LTTNG_EVENT_MMAP, + ) + _enable_events( + session_name=session_name, + domain_type=domain_type, + channel_name=channel_name, + events=ros_events, + ) + _add_contexts( + session_name=session_name, + domain_type=domain_type, + channel_name=channel_name, + context_fields=contexts_dict.get(domain), + ) if kernel_enabled: - handle_kernel = _create_handle(session_name, domain_kernel) - _enable_channel(handle_kernel, channel_kernel) - _enable_events(handle_kernel, events_list_kernel, channel_kernel.name) - - # Context - contexts_dict = _normalize_contexts_dict( - {'kernel': handle_kernel, 'userspace': handle_ust}, context_fields) - _add_context(contexts_dict) + domain = DOMAIN_TYPE_KERNEL + domain_type = lttngpy.LTTNG_DOMAIN_KERNEL + channel_name = channel_name_kernel + _enable_channel( + session_name=session_name, + domain_type=domain_type, + # Global buffer (only option for kernel domain) + buffer_type=lttngpy.LTTNG_BUFFER_GLOBAL, + channel_name=channel_name, + # Discard, do not overwrite + overwrite=0, + # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode, + # and switching between sub-buffers introduces noticeable CPU overhead + subbuf_size=subbuffer_size_kernel, + num_subbuf=2, + # Ignore switch timer interval and use read timer instead + switch_timer_interval=0, + read_timer_interval=200, + # mmap channel output instead of splice + output=lttngpy.LTTNG_EVENT_MMAP, + ) + _enable_events( + session_name=session_name, + domain_type=domain_type, + channel_name=channel_name, + events=kernel_events, + ) + _add_contexts( + session_name=session_name, + domain_type=domain_type, + channel_name=channel_name, + context_fields=contexts_dict.get(domain), + ) return full_path @@ -300,13 +314,14 @@ def start( """ Start LTTng session, and check for errors. + This must not be called if `lttngpy.is_available()` is `False`. Raises RuntimeError on failure to start. :param session_name: the name of the session """ - result = lttng.start(session_name) + result = lttngpy.lttng_start_tracing(session_name=session_name) if result < 0: - raise RuntimeError(f'failed to start tracing: {lttng.strerror(result)}') + raise RuntimeError(f'failed to start tracing: {lttngpy.lttng_strerror(result)}') def stop( @@ -318,14 +333,15 @@ def stop( """ Stop LTTng session, and check for errors. + This must not be called if `lttngpy.is_available()` is `False`. Raises RuntimeError on failure to stop, unless ignored. :param session_name: the name of the session :param ignore_error: whether to ignore any error when stopping """ - result = lttng.stop(session_name) + result = lttngpy.lttng_stop_tracing(session_name=session_name) if result < 0 and not ignore_error: - raise RuntimeError(f'failed to stop tracing: {lttng.strerror(result)}') + raise RuntimeError(f'failed to stop tracing: {lttngpy.lttng_strerror(result)}') def destroy( @@ -337,181 +353,106 @@ def destroy( """ Destroy LTTng session, and check for errors. - Raises RuntimeError on failure to stop, unless ignored. + This must not be called if `lttngpy.is_available()` is `False`. + Raises RuntimeError on failure to destroy, unless ignored. :param session_name: the name of the session :param ignore_error: whether to ignore any error when destroying """ - result = lttng.destroy(session_name) + result = lttngpy.lttng_destroy_session(session_name=session_name) if result < 0 and not ignore_error: - raise RuntimeError(f'failed to destroy tracing session: {lttng.strerror(result)}') - - -def _create_events( - event_names: Set[str], -) -> List[lttng.Event]: - """ - Create events list from names. - - :param event_names: a set of names to create events for - :return: the list of events - """ - events_list = [] - for event_name in event_names: - e = lttng.Event() - e.name = event_name - e.type = lttng.EVENT_TRACEPOINT - e.loglevel_type = lttng.EVENT_LOGLEVEL_ALL - events_list.append(e) - return events_list + raise RuntimeError(f'failed to destroy tracing session: {lttngpy.lttng_strerror(result)}') def _create_session( + *, session_name: str, full_path: str, ) -> None: """ Create session from name and full directory path, and check for errors. + This must not be called if `lttngpy.is_available()` is `False`. + Raises RuntimeError on failure. + :param session_name: the name of the session :param full_path: the full path to the main directory to write trace data to """ - result = lttng.create(session_name, full_path) - # See lttng-tools/include/lttng/lttng-error.h - if -28 == result: - # Sessions seem to persist, so if it already exists, - # just destroy it and try again + result = lttngpy.lttng_create_session( + session_name=session_name, + url=full_path, + ) + if -lttngpy.LTTNG_ERR_EXIST_SESS.value == result: + # Sessions may persist if there was an error previously, so if it already exists, just + # destroy it and try again destroy(session_name=session_name) - result = lttng.create(session_name, full_path) + result = lttngpy.lttng_create_session( + session_name=session_name, + url=full_path, + ) if result < 0: - raise RuntimeError(f'session creation failed: {lttng.strerror(result)}') - + raise RuntimeError(f'session creation failed: {lttngpy.lttng_strerror(result)}') -def _create_handle( - session_name: str, - domain: lttng.Domain, -) -> lttng.Handle: - """ - Create a handle for a given session name and a domain, and check for errors. - :param session_name: the name of the session - :param domain: the domain to be used - :return: the handle +def _enable_channel(**kwargs) -> None: """ - handle = None - handle = lttng.Handle(session_name, domain) - if handle is None: - raise RuntimeError('handle creation failed') - return handle - + Enable channel, and check for errors. -def _enable_channel( - handle: lttng.Handle, - channel: lttng.Channel, -) -> None: - """ - Enable channel for a handle, and check for errors. + This must not be called if `lttngpy.is_available()` is `False`. + Raises RuntimeError on failure. - :param handle: the handle to be used - :param channel: the channel to enable + See `lttngpy.enable_channel` for kwargs. """ - result = lttng.enable_channel(handle, channel) + result = lttngpy.enable_channel(**kwargs) if result < 0: - raise RuntimeError(f'channel enabling failed: {lttng.strerror(result)}') - - -def _enable_events( - handle: lttng.Handle, - events_list: List[lttng.Event], - channel_name: str, -) -> None: - """ - Enable events list for a given handle and channel name, and check for errors. - - :param handle: the handle to be used - :param events_list: the list of events to enable - :param channel_name: the name of the channel to associate - """ - for event in events_list: - result = lttng.enable_event(handle, event, channel_name) - if result < 0: - raise RuntimeError(f'event enabling failed: {lttng.strerror(result)}') + raise RuntimeError(f'channel enabling failed: {lttngpy.lttng_strerror(result)}') -context_map = { - name: getattr(lttng, name_constant, None) if name_constant is not None else None - for name, name_constant in CONTEXT_TYPE_CONSTANTS_MAP.items() -} - - -def _context_field_name_to_type( - context_field_name: str, -) -> Optional[int]: - """ - Convert from context name to LTTng enum/constant type. - - :param context_field_name: the generic name for the context field - :return: the associated type, or `None` if it cannot be found +def _enable_events(**kwargs) -> None: """ - return context_map.get(context_field_name, None) + Enable events for a given channel name, and check for errors. + This must not be called if `lttngpy.is_available()` is `False`. + Raises RuntimeError on failure. -def _create_context_list( - context_fields: Set[str], -) -> List[lttng.EventContext]: + See `lttngpy.enable_events` for kwargs. """ - Create context list from field names, and check for errors. - - :param context_fields: the set of context fields - :return: the event context list - """ - context_list = [] - for context_field_name in context_fields: - ec = lttng.EventContext() - context_type = _context_field_name_to_type(context_field_name) - if context_type is not None: - ec.ctx = context_type - context_list.append(ec) - else: - raise RuntimeError(f'failed to find context type: {context_field_name}') - return context_list + result = lttngpy.enable_events(**kwargs) + if result < 0: + raise RuntimeError(f'event enabling failed: {lttngpy.lttng_strerror(result)}') def _normalize_contexts_dict( - handles: Dict[str, Optional[lttng.Handle]], context_fields: Union[Set[str], Dict[str, List[str]]], -) -> Dict[lttng.Handle, List[lttng.EventContext]]: +) -> Dict[str, Set[str]]: """ - Normalize context list/set/dict to dict. + Normalize context set/dict to dict. - :param handles: the mapping from domain type name to handle - :param context_fields: the set of context field names, - or mapping from domain type name to list of context field names - :return: a dictionary of handle to list of event contexts + :param context_fields: the names of context fields to enable + if it's a set, the context fields are enabled for both kernel and userspace; + if it's a dictionary: { domain type string -> context fields list } + with the domain type string being either 'kernel' or 'userspace' + :return: a dictionary of domain type name to list of context field name """ - handles = {domain: handle for domain, handle in handles.items() if handle is not None} - contexts_dict = {} - if isinstance(context_fields, set): - contexts_dict = {h: _create_context_list(context_fields) for _, h in handles.items()} - elif isinstance(context_fields, dict): - contexts_dict = \ - {h: _create_context_list(set(context_fields[d])) for d, h in handles.items()} - else: - assert False - return contexts_dict - - -def _add_context( - contexts: Dict[lttng.Handle, List[lttng.EventContext]], -) -> None: + if isinstance(context_fields, dict): + return {domain: set(field_names) for domain, field_names in context_fields.items()} + assert isinstance(context_fields, set) + return { + 'userspace': context_fields, + 'kernel': context_fields, + } + + +def _add_contexts(**kwargs) -> None: """ Add context lists to given handles, and check for errors. - :param contexts: the dictionay of context handles -> event contexts + This must not be called if `lttngpy.is_available()` is `False`. + Raises RuntimeError on failure. + + See `lttngpy.add_contexts` for kwargs. """ - for handle, contexts_list in contexts.items(): - for context in contexts_list: - result = lttng.add_context(handle, context, None, None) - if result < 0: - raise RuntimeError( - f'failed to add context field {str(context)}: {lttng.strerror(result)}') + result = lttngpy.add_contexts(**kwargs) + if result < 0: + raise RuntimeError( + f'failed to add context field: {lttngpy.lttng_strerror(result)}') diff --git a/tracetools_trace/tracetools_trace/tools/lttng_stub.py b/tracetools_trace/tracetools_trace/tools/lttng_stub.py index ce7ecc7..2a6b5c0 100644 --- a/tracetools_trace/tracetools_trace/tools/lttng_stub.py +++ b/tracetools_trace/tracetools_trace/tools/lttng_stub.py @@ -15,7 +15,7 @@ """Stub version of the interface for tracing with LTTng.""" -ERROR_MESSAGE = 'lttng module not found, but still tried to use it' +ERROR_MESSAGE = 'LTTng Python bindings not available, but still tried to use them' def setup(*args, **kwargs) -> None: diff --git a/tracetools_trace/tracetools_trace/tools/names.py b/tracetools_trace/tracetools_trace/tools/names.py index 6b3effa..ab5d37a 100644 --- a/tracetools_trace/tracetools_trace/tools/names.py +++ b/tracetools_trace/tracetools_trace/tools/names.py @@ -101,29 +101,8 @@ DEFAULT_EVENTS_UST = DEFAULT_EVENTS_ROS -CONTEXT_TYPE_CONSTANTS_MAP = { - 'pid': 'EVENT_CONTEXT_PID', - 'procname': 'EVENT_CONTEXT_PROCNAME', - 'prio': 'EVENT_CONTEXT_PRIO', - 'nice': 'EVENT_CONTEXT_NICE', - 'vpid': 'EVENT_CONTEXT_VPID', - 'tid': 'EVENT_CONTEXT_TID', - 'vtid': 'EVENT_CONTEXT_VTID', - 'ppid': 'EVENT_CONTEXT_PPID', - 'vppid': 'EVENT_CONTEXT_VPPID', - 'pthread_id': 'EVENT_CONTEXT_PTHREAD_ID', - 'hostname': 'EVENT_CONTEXT_HOSTNAME', - 'ip': 'EVENT_CONTEXT_IP', - 'interruptible': 'EVENT_CONTEXT_INTERRUPTIBLE', - 'preemptible': 'EVENT_CONTEXT_PREEMPTIBLE', - 'need_reschedule': 'EVENT_CONTEXT_NEED_RESCHEDULE', - 'migratable': 'EVENT_CONTEXT_MIGRATABLE', - 'perf:thread:instructions': None, - 'perf:thread:cycles': None, - 'perf:thread:cpu-cycles': None, -} - -CONTEXT = list(CONTEXT_TYPE_CONSTANTS_MAP.keys()) +DOMAIN_TYPE_KERNEL = 'kernel' +DOMAIN_TYPE_USERSPACE = 'userspace' # These apply to both kernel & userspace domains DEFAULT_CONTEXT = [