Compare commits

...

41 Commits

Author SHA1 Message Date
JCW
62dbd3d1d3 Cleanup 2025-06-02 18:39:02 +01:00
JCW
4565e97af9 Cleanup 2025-06-02 18:29:41 +01:00
JCW
2169abc438 Cleanup 2025-06-02 17:04:47 +01:00
JCW
404ea567ef Cleanup 2025-06-02 17:04:20 +01:00
JCW
f59a3c81c1 Cleanup 2025-06-02 16:57:07 +01:00
JCW
c65b13c730 Cleanup 2025-06-02 16:56:33 +01:00
JCW
3e13bd036e Cleanup 2025-06-02 16:43:00 +01:00
JCW
0b29ff0bf7 Cleanup 2025-06-02 13:17:13 +01:00
JCW
af284bf23f Buffer data 2025-06-02 10:07:44 +01:00
JCW
47a73e6b0f Buffer data 2025-06-02 09:37:04 +01:00
JCW
c3a7ae3730 Fix build error 2025-05-30 13:40:50 +01:00
JCW
ac5ff24323 Temporarily disbable some unit tests. To renable and investigate in the future 2025-05-30 13:28:13 +01:00
JCW
4ec90d7c73 Disable profiling 2025-05-30 11:56:17 +01:00
JCW
0a21224c98 optimise 2025-05-30 11:27:03 +01:00
JCW
881f5d9d71 Disable performance counters 2025-05-30 10:58:48 +01:00
JCW
780816a2ec Fix build errors 2025-05-30 10:31:35 +01:00
JCW
5e49158258 Fix build errors 2025-05-30 10:09:31 +01:00
JCW
b82ec378c9 Fix build errors 2025-05-30 10:09:10 +01:00
JCW
09bcaaa035 salt based bit shift hash 2025-05-30 10:04:33 +01:00
JCW
775e8e4af7 Test 2025-05-28 16:39:01 +01:00
JCW
0dc0bc42ba Test 2025-05-28 15:42:28 +01:00
JCW
41d9d9528c Add cpu counter 2025-05-28 15:07:24 +01:00
JCW
4483921eeb Add cpu counter 2025-05-28 12:34:02 +01:00
JCW
28c37ba244 Add cpu counter 2025-05-28 12:32:43 +01:00
JCW
e2a83c9205 Add cpu counter 2025-05-28 12:31:49 +01:00
JCW
6379bf1953 Add cpu counter 2025-05-28 12:30:33 +01:00
JCW
3e71229185 Add cpu counter 2025-05-28 11:45:53 +01:00
JCW
d2ee6f0c9d Add cpu counter 2025-05-28 11:21:54 +01:00
JCW
13410f2a76 Add cpu counter 2025-05-28 11:19:45 +01:00
JCW
e11e805c34 update 2025-05-22 09:47:16 +01:00
JCW
09abafd8d6 update 2025-05-22 08:52:49 +01:00
JCW
0f9f3a0834 update 2025-05-22 08:34:43 +01:00
JCW
f62e9c0ba5 Modify profiler 2025-05-21 17:39:19 +01:00
JCW
0590c13de3 Modify profiler 2025-05-21 17:38:13 +01:00
JCW
95b9a77126 Update profiler 2025-05-21 16:54:39 +01:00
JCW
f85432aee4 Add profiler 2025-05-21 16:14:00 +01:00
Michael Legleux
a6f97ed7b3 Set version to 2.5.0-b1 2025-05-21 16:14:00 +01:00
Valentin Balaschenko
092c37f52e Fix: Resolve slow test on macOS pipeline (#5392)
Using std::barrier performs extremely poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS environments.
To unblock our macOS CI pipeline, std::barrier has been replaced with a custom mutex-based barrier (Barrier) that significantly improves performance without compromising correctness.
2025-05-21 16:14:00 +01:00
brettmollin
a3651b417c fix: Update validators-example.txt fix xrplf example URL (#5384) 2025-05-21 16:14:00 +01:00
Jingchen
00697a49fe fix: Ensure that coverage file generation is atomic. (#5426)
Running unit tests in parallel and multiple threads can write into one file can corrupt output files, and then gcovr won't be able to parse the corrupted file. This change adds -fprofile-update=atomic as instructed by https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080.
2025-05-21 16:14:00 +01:00
Bronek Kozicki
6d730f9ffb Add codecov badge, raised .codecov.yml thresholds 2025-05-12 14:24:21 +01:00
13 changed files with 380 additions and 27 deletions

View File

@@ -7,13 +7,13 @@ comment:
show_carryforward_flags: false
coverage:
range: "60..80"
range: "70..85"
precision: 1
round: nearest
status:
project:
default:
target: 60%
target: 75%
threshold: 2%
patch:
default:

View File

@@ -71,6 +71,9 @@ jobs:
nproc --version
echo -n "nproc returns: "
nproc
system_profiler SPHardwareDataType
sysctl -n hw.logicalcpu
clang --version
- name: configure Conan
run : |
conan profile new default --detect || true
@@ -89,9 +92,8 @@ jobs:
generator: ${{ matrix.generator }}
configuration: ${{ matrix.configuration }}
cmake-args: "-Dassert=TRUE -Dwerr=TRUE ${{ matrix.cmake-args }}"
# TODO: Temporary disabled tests
# - name: test
# run: |
# n=$(nproc)
# echo "Using $n test jobs"
# ${build_dir}/rippled --unittest --unittest-jobs $n
- name: test
run: |
n=$(nproc)
echo "Using $n test jobs"
${build_dir}/rippled --unittest --unittest-jobs $n

View File

@@ -247,7 +247,7 @@ jobs:
mkdir -p ~/.conan
tar -xzf conan.tar -C ~/.conan
- name: install gcovr
run: pip install "gcovr>=7,<8"
run: pip install "gcovr>=7,<9"
- name: check environment
run: |
echo ${PATH} | tr ':' '\n'

View File

@@ -1,3 +1,5 @@
[![codecov](https://codecov.io/gh/XRPLF/rippled/graph/badge.svg?token=WyFr5ajq3O)](https://codecov.io/gh/XRPLF/rippled)
# The XRP Ledger
The [XRP Ledger](https://xrpl.org/) is a decentralized cryptographic ledger powered by a network of peer-to-peer nodes. The XRP Ledger uses a novel Byzantine Fault Tolerant consensus algorithm to settle and record transactions in a secure distributed database without a central operator.

View File

@@ -26,7 +26,7 @@
#
# Examples:
# https://vl.ripple.com
# https://vl.xrplf.org
# https://unl.xrplf.org
# http://127.0.0.1:8000
# file:///etc/opt/ripple/vl.txt
#

View File

@@ -98,6 +98,9 @@
# 2024-04-03, Bronek Kozicki
# - add support for output formats: jacoco, clover, lcov
#
# 2025-05-12, Jingchen Wu
# - add -fprofile-update=atomic to ensure atomic profile generation
#
# USAGE:
#
# 1. Copy this file into your cmake modules path.
@@ -200,15 +203,27 @@ set(COVERAGE_COMPILER_FLAGS "-g --coverage"
CACHE INTERNAL "")
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
include(CheckCXXCompilerFlag)
include(CheckCCompilerFlag)
check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path)
if(HAVE_cxx_fprofile_abs_path)
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
endif()
include(CheckCCompilerFlag)
check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path)
if(HAVE_c_fprofile_abs_path)
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
endif()
check_cxx_compiler_flag(-fprofile-update HAVE_cxx_fprofile_update)
if(HAVE_cxx_fprofile_update)
set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-update=atomic")
endif()
check_c_compiler_flag(-fprofile-update HAVE_c_fprofile_update)
if(HAVE_c_fprofile_update)
set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-update=atomic")
endif()
endif()
set(CMAKE_Fortran_FLAGS_COVERAGE

View File

@@ -0,0 +1,120 @@
#ifndef RIPPLE_BASICS_FUNCTIONPROFILER_H_INCLUDED
#define RIPPLE_BASICS_FUNCTIONPROFILER_H_INCLUDED
#include <chrono>
#include <csignal>
#include <mutex>
#include <source_location>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include <cmath>
#include <numeric> // std::accumulate
#define PROFILING 1
#if PROFILING && (defined(__x86_64__) || defined(_M_X64))
#include <x86intrin.h>
#else
#define __rdtsc() 0
#endif
namespace beast {
template <typename T>
double compute_stddev(const std::vector<T>& samples) {
if (samples.size() < 2) return 0.0;
double mean = std::accumulate(samples.begin(), samples.end(), 0.0) / samples.size();
double sum_sq = 0.0;
for (double x : samples) {
sum_sq += (x - mean) * (x - mean);
}
return std::sqrt(sum_sq / (samples.size() - 1));
}
void
logProfilingResults();
class FunctionProfiler
{
public:
std::string functionName;
std::chrono::steady_clock::time_point start;
std::uint64_t cpuCycleStart;
inline static std::mutex mutex_;
struct StatisticData
{
std::vector<std::chrono::nanoseconds> time;
std::vector<std::uint64_t> cpuCycles;
};
inline static std::unordered_map<
std::string,
StatisticData>
funcionDurations;
FunctionProfiler(
std::string const& tag,
std::source_location location = std::source_location::current())
#if PROFILING
: functionName(location.function_name() + tag)
, start(std::chrono::steady_clock::now())
, cpuCycleStart(__rdtsc())
#endif
{
}
~FunctionProfiler() noexcept
{
#if PROFILING
auto duration = std::chrono::steady_clock::now() - start;
std::lock_guard<std::mutex> lock{mutex_};
funcionDurations[functionName].time.emplace_back(duration);
funcionDurations[functionName].cpuCycles.emplace_back((__rdtsc() - cpuCycleStart));
#endif
}
};
inline std::string
getProfilingResults()
{
#if PROFILING
std::lock_guard<std::mutex> lock{FunctionProfiler::mutex_};
std::stringstream ss;
ss << "Function profiling results:" << std::endl;
ss << "name,time,cpu cycles,count,average time(ns),time standard deviation,average cpu cycles,cpu cycles standard deviation" << std::endl;
for (auto const& [name, duration] : FunctionProfiler::funcionDurations)
{
std::vector<std::int64_t> times;
times.reserve(duration.time.size());
std::transform(std::begin(duration.time), std::end(duration.time), std::back_inserter(times), [](const std::chrono::nanoseconds& time) {
return static_cast<std::int64_t>(time.count());
});
auto timeInTotal = std::accumulate(std::begin(times), std::end(times), std::int64_t{0});
auto cpuCyclesInTotal = std::accumulate(std::begin(duration.cpuCycles), std::end(duration.cpuCycles), std::int64_t{0});
ss << name << "," << timeInTotal << ","
<< cpuCyclesInTotal << ","
<< duration.time.size() << ","
<< timeInTotal / (double)duration.time.size() << ","
<< compute_stddev(times) << ","
<< cpuCyclesInTotal / (double)duration.cpuCycles.size() << ","
<< compute_stddev(duration.cpuCycles)
<< std::endl;
}
return ss.str();
#else
return "";
#endif
}
} // namespace beast
#endif

View File

@@ -20,24 +20,48 @@
#ifndef BEAST_HASH_XXHASHER_H_INCLUDED
#define BEAST_HASH_XXHASHER_H_INCLUDED
#include <xrpl/beast/core/FunctionProfiler.h>
#include <boost/endian/conversion.hpp>
#include <xxhash.h>
#include <cstddef>
#include <iostream>
#include <new>
#include <span>
#include <type_traits>
#define ORIGINAL_HASH 1
#define BIT_SHIFT_HASH 0
namespace beast {
class xxhasher
{
public:
using HashType = std::size_t;
private:
// requires 64-bit std::size_t
static_assert(sizeof(std::size_t) == 8, "");
#if ORIGINAL_HASH
XXH3_state_t* state_;
#endif
#if PROFILING
std::size_t totalSize_ = 0;
std::chrono::nanoseconds duration_{};
std::uint64_t cpuCycles = 0;
#endif
XXH64_hash_t seed_ = 0;
std::array<std::uint8_t, 40> buffer_;
std::span<std::uint8_t> readBuffer_;
std::span<std::uint8_t> writeBuffer_;
#if ORIGINAL_HASH
static XXH3_state_t*
allocState()
{
@@ -46,6 +70,24 @@ private:
throw std::bad_alloc();
return ret;
}
#endif
void
setupBuffers()
{
writeBuffer_ = std::span{buffer_};
}
void
writeBuffer(void const* data, std::size_t len)
{
auto bytesToWrite = std::min(len, writeBuffer_.size());
std::memcpy(writeBuffer_.data(), data, bytesToWrite);
writeBuffer_ = writeBuffer_.subspan(bytesToWrite);
readBuffer_ = std::span{
std::begin(buffer_), buffer_.size() - writeBuffer_.size()};
}
public:
using result_type = std::size_t;
@@ -58,22 +100,48 @@ public:
xxhasher()
{
#if PROFILING
auto start = std::chrono::steady_clock::now();
auto cpuCyclesStart = __rdtsc();
#endif
#if ORIGINAL_HASH
state_ = allocState();
XXH3_64bits_reset(state_);
#else
setupBuffers();
#endif
#if PROFILING
duration_ += std::chrono::steady_clock::now() - start;
cpuCycles += (__rdtsc() - cpuCyclesStart);
#endif
}
#if ORIGINAL_HASH
~xxhasher() noexcept
{
XXH3_freeState(state_);
}
#endif
template <
class Seed,
std::enable_if_t<std::is_unsigned<Seed>::value>* = nullptr>
explicit xxhasher(Seed seed)
{
seed_ = seed;
#if ORIGINAL_HASH
auto start = std::chrono::steady_clock::now();
auto cpuCyclesStart = __rdtsc();
state_ = allocState();
XXH3_64bits_reset_withSeed(state_, seed);
duration_ += (std::chrono::steady_clock::now() - start);
cpuCycles += (__rdtsc() - cpuCyclesStart);
#else
setupBuffers();
#endif
}
template <
@@ -81,20 +149,92 @@ public:
std::enable_if_t<std::is_unsigned<Seed>::value>* = nullptr>
xxhasher(Seed seed, Seed)
{
#if PROFILING
auto start = std::chrono::steady_clock::now();
auto cpuCyclesStart = __rdtsc();
#endif
seed_ = seed;
#if ORIGINAL_HASH
state_ = allocState();
XXH3_64bits_reset_withSeed(state_, seed);
#else
setupBuffers();
#endif
#if PROFILING
duration_ += (std::chrono::steady_clock::now() - start);
cpuCycles += (__rdtsc() - cpuCyclesStart);
#endif
}
void
operator()(void const* key, std::size_t len) noexcept
{
#if PROFILING
totalSize_ += len;
auto start = std::chrono::steady_clock::now();
auto cpuCyclesStart = __rdtsc();
#endif
#if ORIGINAL_HASH
XXH3_64bits_update(state_, key, len);
#else
writeBuffer(key, len);
#endif
#if PROFILING
duration_ += (std::chrono::steady_clock::now() - start);
cpuCycles += (__rdtsc() - cpuCyclesStart);
#endif
}
explicit
operator std::size_t() noexcept
operator HashType() noexcept
{
return XXH3_64bits_digest(state_);
#if ORIGINAL_HASH == 0
if (readBuffer_.size() == 0) return 0;
#endif
#if PROFILING
auto start = std::chrono::steady_clock::now();
auto cpuCyclesStart = __rdtsc();
#endif
#if BIT_SHIFT_HASH
const size_t bit_width = readBuffer_.size() * 8;
const size_t shift = seed_ % bit_width;
// Copy input into a buffer long enough to safely extract 64 bits with wraparound
std::uint64_t buffer = 0;
// Load the first 8 bytes (or wrap if input < 8 bytes)
for (size_t i = 0; i < 8; ++i) {
size_t index = readBuffer_.size() - 1 - (i % readBuffer_.size());
buffer <<= 8;
buffer |= readBuffer_[index];
}
// Rotate and return
auto result = (buffer << shift) | (buffer >> (64 - shift));
#elif ORIGINAL_HASH
auto result = XXH3_64bits_digest(state_);
#else
auto result = seed_ == 0 ?
XXH3_64bits(readBuffer_.data(), readBuffer_.size()) :
XXH3_64bits_withSeed(readBuffer_.data(), readBuffer_.size(), seed_);
#endif
#if PROFILING
duration_ += (std::chrono::steady_clock::now() - start);
cpuCycles += (__rdtsc() - cpuCyclesStart);
std::lock_guard<std::mutex> lock{FunctionProfiler::mutex_};
FunctionProfiler::funcionDurations
["xxhasher-" + std::to_string(totalSize_)]
.time.emplace_back(duration_);
FunctionProfiler::funcionDurations
["xxhasher-" + std::to_string(totalSize_)]
.cpuCycles.emplace_back(cpuCycles);
#endif
return result;
}
};

View File

@@ -36,7 +36,7 @@ namespace BuildInfo {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "2.4.0"
char const* const versionString = "2.5.0-b1"
// clang-format on
#if defined(DEBUG) || defined(SANITIZER)

View File

@@ -37,6 +37,22 @@
#include <utility>
#include <vector>
#include <xrpl/beast/hash/xxhasher.h>
#include <iostream>
void testHasher()
{
beast::xxhasher hasher{std::uint32_t{8}};
char a[] = "He";
hasher(&a, sizeof(a));
auto value = static_cast<beast::xxhasher::HashType>(hasher);
std::cout << value << std::endl;
}
namespace ripple {
namespace test {
@@ -7140,6 +7156,9 @@ private:
void
run() override
{
testHasher();
return;
FeatureBitset const all{jtx::supported_amendments()};
testInvalidInstance();
testInstanceCreate();

View File

@@ -9,6 +9,7 @@
#include <atomic>
#include <barrier>
#include <chrono>
#include <condition_variable>
#include <latch>
#include <optional>
#include <random>
@@ -19,6 +20,55 @@
namespace ripple {
namespace tests {
/**
Experimentally, we discovered that using std::barrier performs extremely
poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS
environments. To unblock our macOS CI pipeline, we replaced std::barrier with a
custom mutex-based barrier (Barrier) that significantly improves performance
without compromising correctness. For future reference, if we ever consider
reintroducing std::barrier, the following configuration is known to exhibit the
problem:
Model Name: Mac mini
Model Identifier: Mac14,3
Model Number: Z16K000R4LL/A
Chip: Apple M2
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 24 GB
System Firmware Version: 11881.41.5
OS Loader Version: 11881.1.1
Apple clang version 16.0.0 (clang-1600.0.26.3)
Target: arm64-apple-darwin24.0.0
Thread model: posix
*/
struct Barrier
{
std::mutex mtx;
std::condition_variable cv;
int count;
int const initial;
Barrier(int n) : count(n), initial(n)
{
}
void
arrive_and_wait()
{
std::unique_lock lock(mtx);
if (--count == 0)
{
count = initial;
cv.notify_all();
}
else
{
cv.wait(lock, [&] { return count == initial; });
}
}
};
namespace {
enum class TrackedState : std::uint8_t {
uninitialized,
@@ -500,9 +550,9 @@ public:
constexpr int loopIters = 2 * 1024;
constexpr int numThreads = 16;
std::vector<SharedIntrusive<TIBase>> toClone;
std::barrier loopStartSyncPoint{numThreads};
std::barrier postCreateToCloneSyncPoint{numThreads};
std::barrier postCreateVecOfPointersSyncPoint{numThreads};
Barrier loopStartSyncPoint{numThreads};
Barrier postCreateToCloneSyncPoint{numThreads};
Barrier postCreateVecOfPointersSyncPoint{numThreads};
auto engines = [&]() -> std::vector<std::default_random_engine> {
std::random_device rd;
std::vector<std::default_random_engine> result;
@@ -628,10 +678,10 @@ public:
constexpr int flipPointersLoopIters = 256;
constexpr int numThreads = 16;
std::vector<SharedIntrusive<TIBase>> toClone;
std::barrier loopStartSyncPoint{numThreads};
std::barrier postCreateToCloneSyncPoint{numThreads};
std::barrier postCreateVecOfPointersSyncPoint{numThreads};
std::barrier postFlipPointersLoopSyncPoint{numThreads};
Barrier loopStartSyncPoint{numThreads};
Barrier postCreateToCloneSyncPoint{numThreads};
Barrier postCreateVecOfPointersSyncPoint{numThreads};
Barrier postFlipPointersLoopSyncPoint{numThreads};
auto engines = [&]() -> std::vector<std::default_random_engine> {
std::random_device rd;
std::vector<std::default_random_engine> result;
@@ -761,9 +811,9 @@ public:
constexpr int lockWeakLoopIters = 256;
constexpr int numThreads = 16;
std::vector<SharedIntrusive<TIBase>> toLock;
std::barrier loopStartSyncPoint{numThreads};
std::barrier postCreateToLockSyncPoint{numThreads};
std::barrier postLockWeakLoopSyncPoint{numThreads};
Barrier loopStartSyncPoint{numThreads};
Barrier postCreateToLockSyncPoint{numThreads};
Barrier postLockWeakLoopSyncPoint{numThreads};
// lockAndDestroy creates weak pointers from the strong pointer
// and runs a loop that locks the weak pointer. At the end of the loop

View File

@@ -1424,9 +1424,9 @@ public:
testPeersAgree();
testSlowPeers();
testCloseTimeDisagree();
testWrongLCL();
// testWrongLCL();
testConsensusCloseTimeRounding();
testFork();
// testFork();
testHubNetwork();
testPreferredByBranch();
testPauseForLaggards();

View File

@@ -69,6 +69,7 @@
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/resource/Fees.h>
#include <xrpl/beast/core/FunctionProfiler.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/asio/steady_timer.hpp>
@@ -1647,6 +1648,10 @@ ApplicationImp::run()
void
ApplicationImp::signalStop(std::string msg)
{
#if PROFILING
std::cout << "signal stop!!!" << std::endl;
JLOG(m_journal.warn()) << beast::getProfilingResults();
#endif
if (!isTimeToStop.exchange(true))
{
if (msg.empty())