diff --git a/CMakeLists.txt b/CMakeLists.txt index 52fa1d868..6e531a059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -365,6 +365,7 @@ check_function_exists(vfork HAVE_VFORK) check_function_exists(backtrace_symbols HAVE_BACKTRACE_SYMBOLS) check_function_exists(pipe2 HAVE_PIPE2) check_function_exists(nice HAVE_NICE) +check_function_exists(malloc_info HAVE_MALLOC_INFO) check_library_exists(dl dladdr "dlfcn.h" HAVE_DLADDR) check_library_exists(execinfo backtrace_symbols "" HAVE_LIBEXECINFO) check_include_file_cxx(cxxabi.h HAVE_CXXABI_H) diff --git a/config.h.cmake b/config.h.cmake index 3ed2ae46d..02ab9d7a0 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -8,6 +8,7 @@ #cmakedefine HAVE_LIBEXECINFO #cmakedefine HAVE_CXXABI_H #cmakedefine HAVE_NICE +#cmakedefine HAVE_MALLOC_INFO #cmakedefine HAVE_EDITLINE #cmakedefine HAVE_SYSTEMD diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index 6f7372c12..c807105da 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -288,6 +288,7 @@ Available permissions for specific URL endpoints: config/query | /v1/config | No | 1 config/modify | /v1/config | No | 512 console | /v1/console | No | 1 + debug | /v1/debug | No | 1 events/<type> | /v1/events | No | 1 objects/query/<type> | /v1/objects | Yes | 1 objects/create/<type> | /v1/objects | No | 1 @@ -2528,6 +2529,72 @@ curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ } ``` +## Memory Usage Analysis + +The GNU libc function `malloc_info(3)` provides memory allocation and usage +statistics of Icinga 2 itself. You can call it directly by sending a `GET` +request to the URL endpoint `/v1/debug/malloc_info`. + +The [API permission](12-icinga2-api.md#icinga2-api-permissions) `debug` is required. + +Example: + +```bash +curl -k -s -S -i -u root:icinga https://localhost:5665/v1/debug/malloc_info +``` + +In contrast to other API endpoints, the response is not JSON, +but the raw XML output from `malloc_info(3)`. See also the +[glibc malloc(3) internals](https://sourceware.org/glibc/wiki/MallocInternals). + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + ## API Clients After its initial release in 2015, community members diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 740b112b4..e2f9973df 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -32,6 +32,7 @@ set(remote_SOURCES infohandler.cpp infohandler.hpp jsonrpc.cpp jsonrpc.hpp jsonrpcconnection.cpp jsonrpcconnection.hpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp + mallocinfohandler.cpp mallocinfohandler.hpp messageorigin.cpp messageorigin.hpp modifyobjecthandler.cpp modifyobjecthandler.hpp objectqueryhandler.cpp objectqueryhandler.hpp diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp new file mode 100644 index 000000000..ac73e3650 --- /dev/null +++ b/lib/remote/mallocinfohandler.cpp @@ -0,0 +1,94 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#include "base/defer.hpp" +#include "remote/filterutility.hpp" +#include "remote/httputility.hpp" +#include "remote/mallocinfohandler.hpp" +#include +#include +#include + +#ifdef HAVE_MALLOC_INFO +# include +# include +#endif /* HAVE_MALLOC_INFO */ + +using namespace icinga; + +REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler); + +bool MallocInfoHandler::HandleRequest( + AsioTlsStream&, + const ApiUser::Ptr& user, + boost::beast::http::request& request, + const Url::Ptr& url, + boost::beast::http::response& response, + const Dictionary::Ptr& params, + boost::asio::yield_context&, + HttpServerConnection& +) +{ + namespace http = boost::beast::http; + + if (url->GetPath().size() != 3) { + return false; + } + + if (request.method() != http::verb::get) { + return false; + } + + FilterUtility::CheckPermission(user, "debug"); + +#ifndef HAVE_MALLOC_INFO + HttpUtility::SendJsonError(response, params, 501, "malloc_info(3) not available."); +#else /* HAVE_MALLOC_INFO */ + char* buf = nullptr; + size_t bufSize = 0; + FILE* f = nullptr; + + Defer release ([&f, &buf]() { + if (f) { + (void)fclose(f); + } + + free(buf); + }); + + f = open_memstream(&buf, &bufSize); + + if (!f) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("open_memstream") + << boost::errinfo_errno(error)); + } + + if (malloc_info(0, f)) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("malloc_info") + << boost::errinfo_errno(error)); + } + + auto closeErr (fclose(f)); + f = nullptr; + + if (closeErr) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fclose") + << boost::errinfo_errno(error)); + } + + response.result(200); + response.set(http::field::content_type, "application/xml"); + response.body() = std::string(buf, bufSize); + response.content_length(response.body().size()); +#endif /* HAVE_MALLOC_INFO */ + + return true; +} diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp new file mode 100644 index 000000000..0e188f3eb --- /dev/null +++ b/lib/remote/mallocinfohandler.hpp @@ -0,0 +1,27 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "remote/httphandler.hpp" + +namespace icinga +{ + +class MallocInfoHandler final : public HttpHandler +{ +public: + DECLARE_PTR_TYPEDEFS(MallocInfoHandler); + + bool HandleRequest( + AsioTlsStream& stream, + const ApiUser::Ptr& user, + boost::beast::http::request& request, + const Url::Ptr& url, + boost::beast::http::response& response, + const Dictionary::Ptr& params, + boost::asio::yield_context& yc, + HttpServerConnection& server + ) override; +}; + +}