diff --git a/CMakeLists.txt b/CMakeLists.txt index e4386cedf..00642c13d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,6 +371,7 @@ 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_function_exists(malloc_trim HAVE_MALLOC_TRIM) check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP) check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) check_library_exists(dl dladdr "dlfcn.h" HAVE_DLADDR) diff --git a/config.h.cmake b/config.h.cmake index 2bfadb4a0..6171df0d7 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -9,6 +9,7 @@ #cmakedefine HAVE_CXXABI_H #cmakedefine HAVE_NICE #cmakedefine HAVE_MALLOC_INFO +#cmakedefine HAVE_MALLOC_TRIM #cmakedefine HAVE_PTHREAD_SET_NAME_NP #cmakedefine HAVE_PTHREAD_SETNAME_NP #cmakedefine HAVE_EDITLINE diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index fad9f0700..b56beb515 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -2594,6 +2594,40 @@ but the raw XML output from `malloc_info(3)`. See also the ``` +### Memory Usage Reduction + +The GNU libc function `malloc_trim(3)` attempts to release free memory +from the main heap arena of Icinga 2 itself. You can call it directly +by sending a `POST` request to the URL endpoint `/v1/debug/malloc_trim`. + +The following parameters may be specified +(either as URL parameters or in a JSON-encoded message body): + + Parameter | Type | Description + ----------|--------|------------- + pad | Number | **Optional.** How many heap bytes to preserve, so the next `malloc(3)` call doesn't need to re-allocate memory again via `sbrk(2)`. Defaults to 0. + +The [API permission](12-icinga2-api.md#icinga2-api-permissions) `debug` is required. + +Example: + +```bash +curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ + -X POST 'https://localhost:5665/v1/debug/malloc_trim?pad=1' +``` + +```json +{ + "results": [ + { + "code": 200.0, + "malloc_trim": 1.0, + "status": "Some memory was released back to the system." + } + ] +} +``` + ## API Clients After its initial release in 2015, community members diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index d8d3298c5..f39a2f56b 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -34,6 +34,7 @@ set(remote_SOURCES jsonrpc.cpp jsonrpc.hpp jsonrpcconnection.cpp jsonrpcconnection.hpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp mallocinfohandler.cpp mallocinfohandler.hpp + malloctrimhandler.cpp malloctrimhandler.hpp messageorigin.cpp messageorigin.hpp modifyobjecthandler.cpp modifyobjecthandler.hpp objectqueryhandler.cpp objectqueryhandler.hpp diff --git a/lib/remote/malloctrimhandler.cpp b/lib/remote/malloctrimhandler.cpp new file mode 100644 index 000000000..bf450ca5c --- /dev/null +++ b/lib/remote/malloctrimhandler.cpp @@ -0,0 +1,84 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#include "remote/filterutility.hpp" +#include "remote/httputility.hpp" +#include "remote/malloctrimhandler.hpp" +#include +#include + +#ifdef HAVE_MALLOC_TRIM +# include +#endif /* HAVE_MALLOC_TRIM */ + +using namespace icinga; + +REGISTER_URLHANDLER("/v1/debug/malloc_trim", MallocTrimHandler); + +bool MallocTrimHandler::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::post) { + return false; + } + + auto rawPad (HttpUtility::GetLastParameter(params, "pad")); + size_t pad = 0; + + if (rawPad.GetType() != ValueEmpty) { + try { + pad = boost::lexical_cast(rawPad); + } catch (const std::exception&) { + HttpUtility::SendJsonError(response, params, 400, + "Invalid 'pad' specified. An integer [0," BOOST_PP_STRINGIZE(SIZE_MAX) "] is required."); + + return true; + } + } + + FilterUtility::CheckPermission(user, "debug"); + +#ifndef HAVE_MALLOC_TRIM + HttpUtility::SendJsonError(response, params, 501, "malloc_trim(3) not available."); +#else /* HAVE_MALLOC_TRIM */ + Dictionary::Ptr result1; + auto ret (malloc_trim(pad)); + + if (ret) { + result1 = new Dictionary({ + { "code", 200 }, + { "malloc_trim", ret }, + { "status", "Some memory was released back to the system." } + }); + } else { + result1 = new Dictionary({ + { "code", 503 }, + { "malloc_trim", ret }, + { "status", "It was not possible to release any memory." } + }); + + response.result(http::status::service_unavailable); + } + + Dictionary::Ptr result = new Dictionary({ + { "results", new Array({ result1 }) } + }); + + HttpUtility::SendJsonBody(response, params, result); +#endif /* HAVE_MALLOC_TRIM */ + + return true; +} diff --git a/lib/remote/malloctrimhandler.hpp b/lib/remote/malloctrimhandler.hpp new file mode 100644 index 000000000..9622d4684 --- /dev/null +++ b/lib/remote/malloctrimhandler.hpp @@ -0,0 +1,27 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "remote/httphandler.hpp" + +namespace icinga +{ + +class MallocTrimHandler final : public HttpHandler +{ +public: + DECLARE_PTR_TYPEDEFS(MallocTrimHandler); + + 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; +}; + +}