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;
+};
+
+}