Compare commits

..

33 Commits

Author SHA1 Message Date
Valentin Balaschenko
1e408cb401 remove inefficient triggers + fix test 2026-02-02 17:57:35 +00:00
Valentin Balaschenko
0e78c8cd5b Merge branch 'develop' into vlntb/malloc-trim 2026-02-02 16:31:19 +00:00
Valentin Balaschenko
5976206f46 sweep only 2 2026-01-19 15:12:49 +00:00
Valentin Balaschenko
26aa1536c1 trim on separate thread 2026-01-16 12:58:30 +00:00
Valentin Balaschenko
98e7d8e3bc leave only sweep 2026-01-16 12:40:20 +00:00
Valentin Balaschenko
3b47a677c3 16MB pad 2026-01-16 12:21:41 +00:00
Valentin Balaschenko
b8c56ba83e 1MB pad 2026-01-16 12:20:55 +00:00
Valentin Balaschenko
dfd832ba7d 256Kb pad 2026-01-16 12:17:08 +00:00
Valentin Balaschenko
421f61feef Merge branch 'develop' into vlntb/malloc-trim 2026-01-15 10:07:28 +00:00
Valentin Balaschenko
99648f7986 added measuring duration and page faults instrumentation 2026-01-14 18:49:12 +00:00
Valentin Balaschenko
e57ffcc2e8 Merge branch 'develop' into vlntb/malloc-trim 2026-01-13 16:56:51 +00:00
Valentin Balaschenko
98cc608889 update namespaces 2026-01-13 16:54:16 +00:00
Valentin Balaschenko
508a9c990d Merge branch 'develop' into vlntb/malloc-trim 2026-01-13 14:59:03 +00:00
Valentin Balaschenko
523eb17883 Merge branch 'develop' into vlntb/malloc-trim 2026-01-08 10:03:28 +00:00
Valentin Balaschenko
521abcc476 Merge branch 'develop' into vlntb/malloc-trim 2026-01-06 16:41:45 +00:00
Valentin Balaschenko
030e64938b Merge branch 'develop' into vlntb/malloc-trim 2025-12-02 10:33:41 -05:00
Valentin Balaschenko
8973ec16ad Merge branch 'develop' into vlntb/malloc-trim 2025-12-01 10:52:29 -05:00
Valentin Balaschenko
645fddaf82 remove unused 2025-11-19 11:48:16 +00:00
Valentin Balaschenko
265ea4b270 Merge branch 'develop' into vlntb/malloc-trim 2025-11-19 11:38:54 +00:00
Valentin Balaschenko
e77bd4e2d8 remove untested 2025-11-19 11:38:18 +00:00
Valentin Balaschenko
6a8a1b7e28 Merge branch 'develop' into vlntb/malloc-trim 2025-11-14 17:33:23 +02:00
Valentin Balaschenko
efe7177d1b load mode with relaxed ordering 2025-11-14 13:07:04 +00:00
Valentin Balaschenko
2b2b361c87 add malloc trim after sync complete 2025-11-14 13:01:38 +00:00
Valentin Balaschenko
ff8b4353bc malloc trim once orderbook update finished 2025-11-14 12:11:20 +00:00
Valentin Balaschenko
50d606539c fixing test 2025-11-13 17:16:53 +00:00
Valentin Balaschenko
d85f7073dd Merge branch 'develop' into vlntb/malloc-trim 2025-11-13 15:57:05 +02:00
Valentin Balaschenko
334382f031 cleanup and notes 2025-11-13 13:56:36 +00:00
Valentin Balaschenko
2d41bfec05 Merge branch 'develop' into vlntb/malloc-trim 2025-11-12 15:36:29 +02:00
Valentin Balaschenko
52c83684cd unit tests + refactore 2025-11-12 13:35:21 +00:00
Valentin Balaschenko
72b34e6615 efficient call from doSweep and online delete 2025-11-11 16:53:02 +00:00
Valentin Balaschenko
a1ed175b66 trim min internal 2025-11-11 16:08:04 +00:00
Valentin Balaschenko
3fdd42af63 encapsulate and instrument 2025-11-11 15:19:50 +00:00
Valentin Balaschenko
ac5554e9f5 testing malloc trim 2025-11-05 21:01:13 +00:00
15 changed files with 537 additions and 28 deletions

View File

@@ -51,20 +51,22 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
# Only generate a subset of configurations in PRs.
if not all:
# Debian:
# - Bookworm using GCC 13: Release on linux/amd64, set the reference
# fee to 500.
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
# coverage (which will be done below).
# - Bookworm using Clang 16: Debug on linux/arm64, enable voidstar.
# - Bookworm using Clang 17: Release on linux/amd64, set the
# reference fee to 1000.
# - Bookworm using Clang 20: Debug on linux/amd64.
# - Bookworm using GCC 13: Release and Unity on linux/amd64, set
# the reference fee to 500.
# - Bookworm using GCC 15: Debug and no Unity on linux/amd64, enable
# code coverage (which will be done below).
# - Bookworm using Clang 16: Debug and no Unity on linux/arm64,
# enable voidstar.
# - Bookworm using Clang 17: Release and no Unity on linux/amd64,
# set the reference fee to 1000.
# - Bookworm using Clang 20: Debug and Unity on linux/amd64.
if os["distro_name"] == "debian":
skip = True
if os["distro_version"] == "bookworm":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
@@ -72,12 +74,14 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/arm64"
):
cmake_args = f"-Dvoidstar=ON {cmake_args}"
@@ -85,6 +89,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
@@ -92,6 +97,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20"
and build_type == "Debug"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -99,14 +105,15 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# RHEL:
# - 9 using GCC 12: Debug on linux/amd64.
# - 10 using Clang: Release on linux/amd64.
# - 9 using GCC 12: Debug and Unity on linux/amd64.
# - 10 using Clang: Release and no Unity on linux/amd64.
if os["distro_name"] == "rhel":
skip = True
if os["distro_version"] == "9":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -114,6 +121,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
and build_type == "Release"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
@@ -121,16 +129,17 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# Ubuntu:
# - Jammy using GCC 12: Debug on linux/arm64.
# - Noble using GCC 14: Release on linux/amd64.
# - Noble using Clang 18: Debug on linux/amd64.
# - Noble using Clang 19: Release on linux/arm64.
# - Jammy using GCC 12: Debug and no Unity on linux/arm64.
# - Noble using GCC 14: Release and Unity on linux/amd64.
# - Noble using Clang 18: Debug and no Unity on linux/amd64.
# - Noble using Clang 19: Release and Unity on linux/arm64.
if os["distro_name"] == "ubuntu":
skip = True
if os["distro_version"] == "jammy":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/arm64"
):
skip = False
@@ -138,18 +147,21 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
and build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "linux/arm64"
):
skip = False
@@ -157,16 +169,20 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
continue
# MacOS:
# - Debug on macos/arm64.
# - Debug and no Unity on macos/arm64.
if os["distro_name"] == "macos" and not (
build_type == "Debug" and architecture["platform"] == "macos/arm64"
build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "macos/arm64"
):
continue
# Windows:
# - Release on windows/amd64.
# - Release and Unity on windows/amd64.
if os["distro_name"] == "windows" and not (
build_type == "Release" and architecture["platform"] == "windows/amd64"
build_type == "Release"
and "-Dunity=ON" in cmake_args
and architecture["platform"] == "windows/amd64"
):
continue
@@ -193,17 +209,18 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
):
continue
# Enable code coverage for Debian Bookworm using GCC 15 in Debug on
# linux/amd64
# Enable code coverage for Debian Bookworm using GCC 15 in Debug and no
# Unity on linux/amd64
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Debug"
and "-Dunity=OFF" in cmake_args
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0 {cmake_args}"
# Generate a unique name for the configuration, e.g. macos-arm64-debug
# or debian-bookworm-gcc-12-amd64-release.
# or debian-bookworm-gcc-12-amd64-release-unity.
config_name = os["distro_name"]
if (n := os["distro_version"]) != "":
config_name += f"-{n}"
@@ -217,6 +234,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
config_name += f"-{build_type.lower()}"
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
config_name += "-unity"
# Add the configuration to the list, with the most unique fields first,
# so that they are easier to identify in the GitHub Actions UI, as long

View File

@@ -208,5 +208,5 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"cmake_args": ["-Dunity=OFF", "-Dunity=ON"]
}

View File

@@ -15,5 +15,8 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
"cmake_args": [
"-Dunity=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"-Dunity=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5"
]
}

View File

@@ -15,5 +15,5 @@
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"cmake_args": ["-Dunity=OFF", "-Dunity=ON"]
}

View File

@@ -545,10 +545,16 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| `assert` | OFF | Enable assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][5] may be faster for the first build
(at the cost of much more memory) since they concatenate sources into fewer
translation units. Non-unity builds may be faster for incremental builds,
and can be helpful for detecting `#include` omissions.
## Troubleshooting
### Conan
@@ -615,6 +621,7 @@ If you want to experiment with a new package, follow these steps:
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
[3]: https://docs.conan.io/en/latest/getting_started.html
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[gcovr]: https://gcovr.com/en/stable/getting-started.html

View File

@@ -9,5 +9,8 @@ function (xrpl_add_test name)
isolate_headers(${target} "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/tests/${name}" PRIVATE)
# Make sure the test isn't optimized away in unity builds
set_target_properties(${target} PROPERTIES UNITY_BUILD_MODE GROUP UNITY_BUILD_BATCH_SIZE 0) # Adjust as needed
add_test(NAME ${target} COMMAND ${target})
endfunction ()

View File

@@ -4,7 +4,12 @@
include(target_protobuf_sources)
# Protocol buffers cannot participate in a unity build,
# because all the generated sources
# define a bunch of `static const` variables with the same names,
# so we just build them as a separate library.
add_library(xrpl.libpb)
set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF)
target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto
PROTOS include/xrpl/proto/xrpl.proto)
@@ -155,4 +160,12 @@ if (xrpld)
# antithesis_instrumentation.h, which is not exported as INTERFACE
target_include_directories(xrpld PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk)
endif ()
# any files that don't play well with unity should be added here
if (tests)
set_source_files_properties(
# these two seem to produce conflicts in beast teardown template methods
src/test/rpc/ValidatorRPC_test.cpp src/test/ledger/Invariants_test.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION
TRUE)
endif ()
endif ()

View File

@@ -30,6 +30,14 @@ if (tests)
endif ()
endif ()
option(unity "Creates a build using UNITY support in cmake." OFF)
if (unity)
if (NOT is_ci)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 15 CACHE STRING "")
endif ()
set(CMAKE_UNITY_BUILD ON CACHE BOOL "Do a unity build")
endif ()
if (is_clang AND is_linux)
option(voidstar "Enable Antithesis instrumentation." OFF)
endif ()

View File

@@ -23,6 +23,7 @@ class Xrpl(ConanFile):
"shared": [True, False],
"static": [True, False],
"tests": [True, False],
"unity": [True, False],
"xrpld": [True, False],
}
@@ -54,6 +55,7 @@ class Xrpl(ConanFile):
"shared": False,
"static": True,
"tests": False,
"unity": False,
"xrpld": False,
"date/*:header_only": True,
"ed25519/*:shared": False,
@@ -166,6 +168,7 @@ class Xrpl(ConanFile):
tc.variables["rocksdb"] = self.options.rocksdb
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
tc.variables["static"] = self.options.static
tc.variables["unity"] = self.options.unity
tc.variables["xrpld"] = self.options.xrpld
tc.generate()

View File

@@ -0,0 +1,75 @@
#ifndef XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#define XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#include <xrpl/beast/utility/Journal.h>
#include <optional>
#include <string>
namespace xrpl {
// cSpell:ignore ptmalloc
// -----------------------------------------------------------------------------
// Allocator interaction note:
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
// ptmalloc return free heap pages to the OS.
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
// on the *active* heap. The call is harmless but may not reclaim memory
// because those allocators manage their own arenas.
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
// allocations are usually returned to the OS on free regardless of trimming.
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
// and consider rate limiting to avoid churn.
// -----------------------------------------------------------------------------
struct MallocTrimReport
{
bool supported{false};
int trimResult{-1};
long rssBeforeKB{-1};
long rssAfterKB{-1};
long long durationUs{-1};
long minfltDelta{-1};
long majfltDelta{-1};
[[nodiscard]] long
deltaKB() const noexcept
{
if (rssBeforeKB < 0 || rssAfterKB < 0)
return 0;
return rssAfterKB - rssBeforeKB;
}
};
/**
* @brief Attempt to return freed memory to the operating system.
*
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
* a different allocator is in use, this function is a no-op and the report will
* indicate that trimming is unsupported or had no effect.
*
* @param tag Optional identifier for logging/debugging purposes.
* @param journal Journal for diagnostic logging.
* @return Report containing before/after metrics and the trim result.
*
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
* calling glibc's malloc_trim may have no effect on the active heap. The
* call is harmless but typically does not reclaim memory under those
* allocators.
*
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
* Large allocations satisfied via mmap are usually returned on free
* independently of trimming.
*
* @note Intended for use after operations that free significant memory (e.g.,
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
*/
MallocTrimReport
mallocTrim(std::optional<std::string> const& tag, beast::Journal journal);
} // namespace xrpl
#endif

View File

@@ -1,5 +1,5 @@
#ifndef XRPL_BASICS_ROCKSDB_H_INCLUDED
#define XRPL_BASICS_ROCKSDB_H_INCLUDED
#ifndef XRPL_UNITY_ROCKSDB_H_INCLUDED
#define XRPL_UNITY_ROCKSDB_H_INCLUDED
#if XRPL_ROCKSDB_AVAILABLE
// #include <rocksdb2/port/port_posix.h>

View File

@@ -0,0 +1,173 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <chrono>
#include <cstdio>
#include <fstream>
// #include <thread>
#if defined(__GLIBC__) && BOOST_OS_LINUX
#include <sys/resource.h>
#include <malloc.h>
#include <unistd.h>
// Require RUSAGE_THREAD for thread-scoped page fault tracking
#ifndef RUSAGE_THREAD
#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc"
#endif
namespace {
pid_t const cachedPid = ::getpid();
bool
getRusageThread(struct rusage& ru)
{
return ::getrusage(RUSAGE_THREAD, &ru) == 0;
}
} // namespace
#endif
namespace xrpl {
namespace detail {
#if defined(__GLIBC__) && BOOST_OS_LINUX
long
parseVmRSSkB(std::string const& status)
{
std::istringstream iss(status);
std::string line;
while (std::getline(iss, line))
{
// Allow leading spaces/tabs before the key.
auto const firstNonWs = line.find_first_not_of(" \t");
if (firstNonWs == std::string::npos)
continue;
constexpr char key[] = "VmRSS:";
constexpr auto keyLen = sizeof(key) - 1;
// Require the line (after leading whitespace) to start with "VmRSS:".
// Check if we have enough characters and the substring matches.
if (firstNonWs + keyLen > line.size() ||
line.substr(firstNonWs, keyLen) != key)
continue;
// Move past "VmRSS:" and any following whitespace.
auto pos = firstNonWs + keyLen;
while (pos < line.size() &&
std::isspace(static_cast<unsigned char>(line[pos])))
{
++pos;
}
long value = -1;
if (std::sscanf(line.c_str() + pos, "%ld", &value) == 1)
return value;
// Found the key but couldn't parse a number.
return -1;
}
// No VmRSS line found.
return -1;
}
#endif // __GLIBC__ && BOOST_OS_LINUX
} // namespace detail
MallocTrimReport
mallocTrim(
[[maybe_unused]] std::optional<std::string> const& tag,
beast::Journal journal)
{
MallocTrimReport report;
#if !(defined(__GLIBC__) && BOOST_OS_LINUX)
JLOG(journal.debug()) << "malloc_trim not supported on this platform";
#else
report.supported = true;
// Launch malloc_trim on a separate thread (async, fire-and-forget)
// std::thread trimThread([tag, journal]() {
if (journal.debug())
{
auto readFile = [](std::string const& path) -> std::string {
std::ifstream ifs(path);
if (!ifs.is_open())
return {};
return std::string(
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
};
std::string const tagStr = tag.value_or("default");
std::string const statusPath =
"/proc/" + std::to_string(cachedPid) + "/status";
auto const statusBefore = readFile(statusPath);
long const rssBeforeKB = detail::parseVmRSSkB(statusBefore);
struct rusage ru0
{
};
bool const have_ru0 = getRusageThread(ru0);
auto const t0 = std::chrono::steady_clock::now();
int const trimResult = ::malloc_trim(0);
auto const t1 = std::chrono::steady_clock::now();
struct rusage ru1
{
};
bool const have_ru1 = getRusageThread(ru1);
auto const statusAfter = readFile(statusPath);
long const rssAfterKB = detail::parseVmRSSkB(statusAfter);
long long const durationUs =
std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0)
.count();
long minfltDelta = -1;
long majfltDelta = -1;
if (have_ru0 && have_ru1)
{
minfltDelta = ru1.ru_minflt - ru0.ru_minflt;
majfltDelta = ru1.ru_majflt - ru0.ru_majflt;
}
long const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0)
? 0
: (rssAfterKB - rssBeforeKB);
JLOG(journal.debug())
<< "malloc_trim tag=" << tagStr << " result=" << trimResult
<< " rss_before=" << rssBeforeKB << "kB"
<< " rss_after=" << rssAfterKB << "kB"
<< " delta=" << deltaKB << "kB"
<< " duration_us=" << durationUs << " minflt_delta=" << minfltDelta
<< " majflt_delta=" << majfltDelta;
}
else
{
::malloc_trim(0);
}
// });
// trimThread.detach();
#endif
return report;
}
} // namespace xrpl

View File

@@ -85,8 +85,7 @@ registerSSLCerts(boost::asio::ssl::context& ctx, boost::system::error_code& ec,
// There is a very unpleasant interaction between <wincrypt> and
// openssl x509 types (namely the former has macros that stomp
// on the latter), these undefs allow this TU to be safely used in
// unity builds without messing up subsequent TUs. Although we
// no longer use unity builds, leaving the undefs here does no harm.
// unity builds without messing up subsequent TUs.
#if BOOST_OS_WINDOWS
#undef X509_NAME
#undef X509_EXTENSIONS

View File

@@ -0,0 +1,203 @@
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <gtest/gtest.h>
using namespace xrpl;
#if defined(__GLIBC__) && BOOST_OS_LINUX
namespace xrpl::detail {
long
parseVmRSSkB(std::string const& status);
} // namespace xrpl::detail
#endif
TEST(MallocTrimReport, structure)
{
// Test default construction
MallocTrimReport report;
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.rssBeforeKB, -1);
EXPECT_EQ(report.rssAfterKB, -1);
EXPECT_EQ(report.deltaKB(), 0);
// Test deltaKB calculation - memory freed
report.rssBeforeKB = 1000;
report.rssAfterKB = 800;
EXPECT_EQ(report.deltaKB(), -200);
// Test deltaKB calculation - memory increased
report.rssBeforeKB = 500;
report.rssAfterKB = 600;
EXPECT_EQ(report.deltaKB(), 100);
// Test deltaKB calculation - no change
report.rssBeforeKB = 1234;
report.rssAfterKB = 1234;
EXPECT_EQ(report.deltaKB(), 0);
}
#if defined(__GLIBC__) && BOOST_OS_LINUX
TEST(parseVmRSSkB, standard_format)
{
using xrpl::detail::parseVmRSSkB;
// Test standard format
{
std::string status = "VmRSS: 123456 kB\n";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, 123456);
}
// Test with multiple lines
{
std::string status =
"Name: xrpld\n"
"VmPeak: 1234567 kB\n"
"VmSize: 1234567 kB\n"
"VmRSS: 987654 kB\n"
"VmData: 123456 kB\n";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, 987654);
}
// Test with minimal whitespace
{
std::string status = "VmRSS: 42 kB";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, 42);
}
// Test with extra whitespace
{
std::string status = "VmRSS: 999999 kB";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, 999999);
}
// Test with tabs
{
std::string status = "VmRSS:\t\t12345 kB";
long result = parseVmRSSkB(status);
// Note: tabs are not explicitly handled as spaces, this documents
// current behavior
EXPECT_EQ(result, 12345);
}
// Test zero value
{
std::string status = "VmRSS: 0 kB\n";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, 0);
}
// Test missing VmRSS
{
std::string status =
"Name: xrpld\n"
"VmPeak: 1234567 kB\n"
"VmSize: 1234567 kB\n";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, -1);
}
// Test empty string
{
std::string status = "";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, -1);
}
// Test malformed data (VmRSS but no number)
{
std::string status = "VmRSS: \n";
long result = parseVmRSSkB(status);
// sscanf should fail to parse and return -1 unchanged
EXPECT_EQ(result, -1);
}
// Test malformed data (VmRSS but invalid number)
{
std::string status = "VmRSS: abc kB\n";
long result = parseVmRSSkB(status);
// sscanf should fail and return -1 unchanged
EXPECT_EQ(result, -1);
}
// Test partial match (should not match "NotVmRSS:")
{
std::string status = "NotVmRSS: 123456 kB\n";
long result = parseVmRSSkB(status);
EXPECT_EQ(result, -1);
}
}
#endif
TEST(mallocTrim, basic_functionality)
{
beast::Journal journal{beast::Journal::getNullSink()};
// Test with no tag
{
MallocTrimReport report = mallocTrim(std::nullopt, journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
// On Linux with glibc, should be supported
EXPECT_EQ(report.supported, true);
// trimResult should be 0 or 1 (success indicators)
EXPECT_GE(report.trimResult, 0);
#else
// On other platforms, should be unsupported
EXPECT_EQ(report.supported, false);
EXPECT_EQ(report.trimResult, -1);
EXPECT_EQ(report.rssBeforeKB, -1);
EXPECT_EQ(report.rssAfterKB, -1);
#endif
}
// Test with tag
{
MallocTrimReport report = mallocTrim(std::optional<std::string>("test_tag"), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else
EXPECT_EQ(report.supported, false);
#endif
}
}
TEST(mallocTrim, debug_logging)
{
beast::Journal journal{beast::Journal::getNullSink()};
MallocTrimReport report = mallocTrim(std::optional<std::string>("debug_test"), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
// The function should complete without crashing
#else
EXPECT_EQ(report.supported, false);
#endif
}
TEST(mallocTrim, repeated_calls)
{
beast::Journal journal{beast::Journal::getNullSink()};
// Call malloc_trim multiple times to ensure it's safe
for (int i = 0; i < 5; ++i)
{
MallocTrimReport report = mallocTrim(std::optional<std::string>("iteration_" + std::to_string(i)), journal);
#if defined(__GLIBC__) && BOOST_OS_LINUX
EXPECT_EQ(report.supported, true);
EXPECT_GE(report.trimResult, 0);
#else
EXPECT_EQ(report.supported, false);
#endif
}
}

View File

@@ -36,6 +36,7 @@
#include <xrpld/shamap/NodeFamily.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/MallocTrim.h>
#include <xrpl/basics/ResolverAsio.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/asio/io_latency_probe.h>
@@ -990,6 +991,8 @@ public:
<< "; size after: " << cachedSLEs_.size();
}
mallocTrim(std::optional<std::string>("doSweep"), m_journal);
// Set timer to do another sweep later.
setSweepTimer();
}