From 19631a6d9212949a363d400cd076fb0d4f5af862 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Fri, 10 Jul 2026 16:21:53 -0400 Subject: [PATCH 1/8] fix: Add perf tests for escrow create and escrow finish --- src/test/app/Wasm_test.cpp | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index 537b8b2330e..a564f89850f 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1544,11 +1544,73 @@ struct Wasm_test : public beast::unit_test::Suite } } + template + void + perf(size_t runs, Functor&& f) + { + using std::chrono::duration_cast; + using std::chrono::steady_clock; + + // Warm up first. + for (auto i = size_t{}; i < 10; ++i) + { + BEAST_EXPECT(f()); + } + + auto totalTime = uint64_t{}; + for (auto i = size_t{}; i < runs; ++i) + { + auto const start = steady_clock::now(); + auto result = f(); + auto const end = steady_clock::now(); + totalTime += duration_cast(end - start).count(); + BEAST_EXPECT(result); + } + log << "Average time for " << runs << " runs: " << (totalTime / runs) << " ns" << std::endl; + } + + void + perfEscrowFinish() + { + testcase("perf escrow finish"); + using namespace test::jtx; + + static constexpr auto kRuns = 1000; + + auto const wasm = hexToBytes(kAllHostFunctionsWasmHex); + + Env env{*this}; + auto hfns = TestHostFunctions{env, 0}; + + perf(kRuns, [&] { + return runEscrowWasm(wasm, hfns, 1'000'000, escrowFunctionName, {}).has_value(); + }); + } + + void + perfEscrowCreate() + { + testcase("perf escrow create"); + using namespace test::jtx; + + static constexpr auto kRuns = 1000; + + auto const wasm = hexToBytes(kAllHostFunctionsWasmHex); + + Env env{*this}; + auto mock = HostFunctions{env.journal}; + + perf(kRuns, [&] { return !preflightEscrowWasm(wasm, mock, escrowFunctionName); }); + } + void run() override { using namespace test::jtx; + perfEscrowFinish(); + perfEscrowCreate(); + testGetDataHelperFunctions(); testWasmLib(); testBadWasm(); From e70623d0cb5f7d1c90364a618d652613a2944e43 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Fri, 10 Jul 2026 16:43:30 -0400 Subject: [PATCH 2/8] fix: Add perf tests for escrow create and escrow finish --- src/test/app/Wasm_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index a564f89850f..10fc45c5ed0 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1597,7 +1597,7 @@ struct Wasm_test : public beast::unit_test::Suite auto const wasm = hexToBytes(kAllHostFunctionsWasmHex); - Env env{*this}; + Env const env{*this}; auto mock = HostFunctions{env.journal}; perf(kRuns, [&] { return !preflightEscrowWasm(wasm, mock, escrowFunctionName); }); From 1825f38a1092f31bf451de8cdb001771014fb76e Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Fri, 10 Jul 2026 23:06:22 -0400 Subject: [PATCH 3/8] fix: Add perf tests for escrow create and escrow finish --- src/test/app/Wasm_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index 10fc45c5ed0..fd164feefed 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -1566,7 +1567,7 @@ struct Wasm_test : public beast::unit_test::Suite totalTime += duration_cast(end - start).count(); BEAST_EXPECT(result); } - log << "Average time for " << runs << " runs: " << (totalTime / runs) << " ns" << std::endl; + log << "Average time for " << runs << " runs: " << (totalTime / runs) << " ns\n"; } void From d88f7d8f683887cee090036bccc5eb3a63070aed Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Thu, 16 Jul 2026 11:11:32 -0400 Subject: [PATCH 4/8] feat: Use beast metrics --- bin/pre-commit/clang_tidy_check.py | 14 +++++++++++- include/xrpl/core/CollectorManager.h | 24 ++++++++++++++++++++ include/xrpl/tx/wasm/HostFunc.h | 7 ++++++ include/xrpl/tx/wasm/HostFuncImpl.h | 10 +++++++++ src/libxrpl/tx/wasm/WasmVM.cpp | 17 ++++++++++++++- src/libxrpl/tx/wasm/WasmiVM.cpp | 8 +++---- src/test/app/TestHostFunctions.h | 29 +++++++++++++++++++++++++ src/test/app/Wasm_test.cpp | 25 +++++---------------- src/xrpld/app/main/CollectorManager.cpp | 1 + src/xrpld/app/main/CollectorManager.h | 26 ++++------------------ 10 files changed, 114 insertions(+), 47 deletions(-) create mode 100644 include/xrpl/core/CollectorManager.h diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py index 7fb51d1c460..f3c75033bb3 100755 --- a/bin/pre-commit/clang_tidy_check.py +++ b/bin/pre-commit/clang_tidy_check.py @@ -168,7 +168,19 @@ def main(): if not os.environ.get("TIDY"): return 0 - repo_root = Path(__file__).parent.parent + # Derive the repo root from git so this keeps working regardless of where + # under the tree this script lives. Fall back to the script location + # (bin/pre-commit/ is two levels below the repo root) if git is unavailable. + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + cwd=Path(__file__).parent, + ) + if result.returncode == 0: + repo_root = Path(result.stdout.strip()) + else: + repo_root = Path(__file__).parent.parent.parent files = staged_files(repo_root) if not files: return 0 diff --git a/include/xrpl/core/CollectorManager.h b/include/xrpl/core/CollectorManager.h new file mode 100644 index 00000000000..e736ae57db0 --- /dev/null +++ b/include/xrpl/core/CollectorManager.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace xrpl { + +/** Provides the beast::insight::Collector service. */ +class CollectorManager +{ +public: + virtual ~CollectorManager() = default; + + virtual beast::insight::Collector::ptr const& + collector() = 0; + + virtual beast::insight::Group::ptr const& + group(std::string const& name) = 0; +}; + +std::unique_ptr +makeCollectorManager(Section const& params, beast::Journal journal); + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFunc.h b/include/xrpl/tx/wasm/HostFunc.h index 7c5eb18b0c8..4f989ba66e3 100644 --- a/include/xrpl/tx/wasm/HostFunc.h +++ b/include/xrpl/tx/wasm/HostFunc.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -121,6 +122,12 @@ struct HostFunctions return j; } + [[nodiscard]] virtual beast::insight::Event + executionTimeEvent(std::string_view name) const + { + return {}; + } + [[nodiscard]] virtual bool checkSelf() const { diff --git a/include/xrpl/tx/wasm/HostFuncImpl.h b/include/xrpl/tx/wasm/HostFuncImpl.h index 689ce85a590..a3f35115791 100644 --- a/include/xrpl/tx/wasm/HostFuncImpl.h +++ b/include/xrpl/tx/wasm/HostFuncImpl.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -77,6 +78,15 @@ class WasmHostFunctionsImpl : public HostFunctions return rt_; } + beast::insight::Event + executionTimeEvent(std::string_view name) const override + { + return ctx_.registry.get() + .getCollectorManager() + .group(std::string{name.data(), name.size()}) + ->makeEvent("finish_time"); + } + bool checkSelf() const override { diff --git a/src/libxrpl/tx/wasm/WasmVM.cpp b/src/libxrpl/tx/wasm/WasmVM.cpp index 6aa26a1f9fb..2ab277e49b7 100644 --- a/src/libxrpl/tx/wasm/WasmVM.cpp +++ b/src/libxrpl/tx/wasm/WasmVM.cpp @@ -6,6 +6,7 @@ #include // IWYU pragma: keep #include +#include #include #include #include @@ -126,9 +127,16 @@ runEscrowWasm( auto& vm = WasmEngine::instance(); // vm.initMaxPages(MAX_PAGES); + auto const start = std::chrono::steady_clock::now(); + auto const ret = vm.run(wasmCode, hfs, gasLimit, funcName, params, createWasmImport(hfs), hfs.getJournal()); + hfs.executionTimeEvent("runEscrowWasm") + .notify( + std::chrono::duration_cast( + std::chrono::steady_clock::now() - start)); + if (!ret) { #ifdef DEBUG_OUTPUT @@ -140,7 +148,7 @@ runEscrowWasm( #ifdef DEBUG_OUTPUT std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost << std::endl; #endif - return EscrowResult{ret->result, ret->cost}; + return EscrowResult{.result = ret->result, .cost = ret->cost}; } NotTEC @@ -154,9 +162,16 @@ preflightEscrowWasm( auto& vm = WasmEngine::instance(); // vm.initMaxPages(MAX_PAGES); + auto const start = std::chrono::steady_clock::now(); + auto const ret = vm.check(wasmCode, hfs, funcName, params, createWasmImport(hfs), hfs.getJournal()); + hfs.executionTimeEvent("preflightEscrowWasm") + .notify( + std::chrono::duration_cast( + std::chrono::steady_clock::now() - start)); + return ret; } diff --git a/src/libxrpl/tx/wasm/WasmiVM.cpp b/src/libxrpl/tx/wasm/WasmiVM.cpp index e22380741ba..e1747e3578a 100644 --- a/src/libxrpl/tx/wasm/WasmiVM.cpp +++ b/src/libxrpl/tx/wasm/WasmiVM.cpp @@ -277,7 +277,7 @@ InstanceWrapper::setGas(std::int64_t gas) const ModulePtr ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) { - wasm_byte_vec_t const code{wasmBin.size(), (char*)(wasmBin.data())}; + wasm_byte_vec_t const code{.size = wasmBin.size(), .data = (char*)(wasmBin.data())}; ModulePtr m = ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete); if (!m) throw std::runtime_error("can't create module"); @@ -711,8 +711,8 @@ WasmiResult WasmiEngine::call(FuncInfo const& f, std::vector& in) { WasmiResult ret(NR); - wasm_val_vec_t const inv = - in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC : wasm_val_vec_t{in.size(), in.data()}; + wasm_val_vec_t const inv = in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC + : wasm_val_vec_t{.size = in.size(), .data = in.data()}; #ifdef SHOW_CALL_TIME auto const start = usecs(); @@ -960,7 +960,7 @@ wasm_trap_t* WasmiEngine::newTrap(std::string const& txt) { static char empty[1] = {0}; - wasm_message_t msg = {1, empty}; + wasm_message_t msg = {.size = 1, .data = empty}; if (!txt.empty()) wasm_name_new(&msg, txt.size() + 1, txt.c_str()); // include 0 diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h index 26d30dd53c3..80e0abae144 100644 --- a/src/test/app/TestHostFunctions.h +++ b/src/test/app/TestHostFunctions.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -18,6 +20,23 @@ namespace xrpl::test { +/** + * Lets a test assert that the WASM execution-timing path fired (and inspect + * the recorded duration) without needing a StatsD sink or a full Collector. + */ +struct RecordingEventImpl : public beast::insight::EventImpl +{ + std::size_t count = 0; + value_type last{}; + + void + notify(value_type const& value) override + { + ++count; + last = value; + } +}; + struct TestLedgerDataProvider : public HostFunctions { jtx::Env& env; @@ -54,6 +73,7 @@ struct TestHostFunctions : public HostFunctions Bytes data; int clock_drift = 0; void* rt = nullptr; + std::shared_ptr execTimeEvent = std::make_shared(); public: TestHostFunctions(test::jtx::Env& env, int cd = 0) @@ -76,6 +96,15 @@ struct TestHostFunctions : public HostFunctions return rt; } + // Return an Event backed by our recording impl so a test can assert that + // the WASM execution was timed. The name is ignored -- every call records + // into the same impl. + [[nodiscard]] beast::insight::Event + executionTimeEvent(std::string_view name) const override + { + return beast::insight::Event(execTimeEvent); + } + Expected getLedgerSqn() const override { diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index fd164feefed..e39bb98769e 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -1547,27 +1546,14 @@ struct Wasm_test : public beast::unit_test::Suite template void - perf(size_t runs, Functor&& f) + perf(TestHostFunctions& hfs, size_t runs, Functor&& f) { - using std::chrono::duration_cast; - using std::chrono::steady_clock; - - // Warm up first. - for (auto i = size_t{}; i < 10; ++i) - { - BEAST_EXPECT(f()); - } - - auto totalTime = uint64_t{}; for (auto i = size_t{}; i < runs; ++i) { - auto const start = steady_clock::now(); auto result = f(); - auto const end = steady_clock::now(); - totalTime += duration_cast(end - start).count(); BEAST_EXPECT(result); } - log << "Average time for " << runs << " runs: " << (totalTime / runs) << " ns\n"; + BEAST_EXPECT(hfs.execTimeEvent->count == runs); } void @@ -1583,7 +1569,7 @@ struct Wasm_test : public beast::unit_test::Suite Env env{*this}; auto hfns = TestHostFunctions{env, 0}; - perf(kRuns, [&] { + perf(hfns, kRuns, [&] { return runEscrowWasm(wasm, hfns, 1'000'000, escrowFunctionName, {}).has_value(); }); } @@ -1598,10 +1584,11 @@ struct Wasm_test : public beast::unit_test::Suite auto const wasm = hexToBytes(kAllHostFunctionsWasmHex); - Env const env{*this}; + Env env{*this}; auto mock = HostFunctions{env.journal}; + auto hfns = TestHostFunctions{env, 0}; - perf(kRuns, [&] { return !preflightEscrowWasm(wasm, mock, escrowFunctionName); }); + perf(hfns, kRuns, [&] { return !preflightEscrowWasm(wasm, mock, escrowFunctionName); }); } void diff --git a/src/xrpld/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp index 6cdbca8d8ac..f025fd0f572 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/app/main/CollectorManager.h b/src/xrpld/app/main/CollectorManager.h index e736ae57db0..c3b1530763e 100644 --- a/src/xrpld/app/main/CollectorManager.h +++ b/src/xrpld/app/main/CollectorManager.h @@ -1,24 +1,6 @@ #pragma once -#include -#include - -namespace xrpl { - -/** Provides the beast::insight::Collector service. */ -class CollectorManager -{ -public: - virtual ~CollectorManager() = default; - - virtual beast::insight::Collector::ptr const& - collector() = 0; - - virtual beast::insight::Group::ptr const& - group(std::string const& name) = 0; -}; - -std::unique_ptr -makeCollectorManager(Section const& params, beast::Journal journal); - -} // namespace xrpl +// The CollectorManager interface moved to so it +// is visible to libxrpl (which cannot depend on xrpld). This shim preserves the +// historical include path for existing xrpld consumers. +#include From d430010b36ea3431f625e35058d4f5fc3a50976e Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Thu, 16 Jul 2026 11:22:13 -0400 Subject: [PATCH 5/8] feat: Add metrics around wasm calls --- include/xrpl/server/detail/Door.h | 8 ++++---- src/xrpld/app/main/Application.cpp | 2 +- src/xrpld/app/main/CollectorManager.cpp | 4 ++-- src/xrpld/app/main/CollectorManager.h | 6 ------ src/xrpld/rpc/ServerHandler.h | 2 +- src/xrpld/rpc/detail/ServerHandler.cpp | 18 +++++++++--------- src/xrpld/shamap/NodeFamily.cpp | 2 +- src/xrpld/shamap/NodeFamily.h | 3 +-- 8 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 src/xrpld/app/main/CollectorManager.h diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index d3fff8b4763..8f171b00ae0 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -90,11 +90,11 @@ class Door : public IOList::Work, public std::enable_shared_from_this strand_; bool ssl_{ - port_.protocol.count("https") > 0 || port_.protocol.count("wss") > 0 || - port_.protocol.count("wss2") > 0 || port_.protocol.count("peer") > 0}; + port_.protocol.contains("https") || port_.protocol.contains("wss") || + port_.protocol.contains("wss2") || port_.protocol.contains("peer")}; bool plain_{ - port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 || - (port_.protocol.count("ws2") != 0u)}; + port_.protocol.contains("http") || port_.protocol.contains("ws") || + (port_.protocol.contains("ws2"))}; static constexpr std::chrono::milliseconds kInitialAcceptDelay{50}; static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000}; std::chrono::milliseconds accept_delay_{kInitialAcceptDelay}; diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 181e2210caa..c3ae1fc9ab5 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -57,6 +56,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp index f025fd0f572..b9bd3cc2a53 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -1,4 +1,5 @@ -#include + +#include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/src/xrpld/app/main/CollectorManager.h b/src/xrpld/app/main/CollectorManager.h deleted file mode 100644 index c3b1530763e..00000000000 --- a/src/xrpld/app/main/CollectorManager.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -// The CollectorManager interface moved to so it -// is visible to libxrpl (which cannot depend on xrpld). This shim preserves the -// historical include path for existing xrpld consumers. -#include diff --git a/src/xrpld/rpc/ServerHandler.h b/src/xrpld/rpc/ServerHandler.h index 6b4cc34cc57..c6849c288a7 100644 --- a/src/xrpld/rpc/ServerHandler.h +++ b/src/xrpld/rpc/ServerHandler.h @@ -1,9 +1,9 @@ #pragma once #include -#include #include +#include #include #include #include diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 2ed6630af22..ea9a574e345 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -165,10 +165,10 @@ ServerHandler::setup(Setup const& setup, beast::Journal journal) port.port = endpointPort; if ((setup_.client.port == 0u) && - (port.protocol.count("http") > 0 || port.protocol.count("https") > 0)) + (port.protocol.contains("http") || port.protocol.contains("https"))) setup_.client.port = endpointPort; - if ((setup_.overlay.port() == 0u) && (port.protocol.count("peer") > 0)) + if ((setup_.overlay.port() == 0u) && (port.protocol.contains("peer"))) setup_.overlay.port(endpointPort); } } @@ -217,7 +217,7 @@ ServerHandler::onHandoff( using namespace boost::beast; auto const& p{session.port().protocol}; bool const isWs{ - p.count("ws") > 0 || p.count("ws2") > 0 || p.count("wss") > 0 || p.count("wss2") > 0}; + p.contains("ws") || p.contains("ws2") || p.contains("wss") || p.contains("wss2")}; if (websocket::is_upgrade(request)) { @@ -251,7 +251,7 @@ ServerHandler::onHandoff( return handoff; } - if (bundle && p.count("peer") > 0) + if (bundle && p.contains("peer")) return app_.getOverlay().onHandoff(std::move(bundle), std::move(request), remoteAddress); if (isWs && isStatusRequest(request)) @@ -301,7 +301,7 @@ void ServerHandler::onRequest(Session& session) { // Make sure RPC is enabled on the port - if (session.port().protocol.count("http") == 0 && session.port().protocol.count("https") == 0) + if (!session.port().protocol.contains("http") && !session.port().protocol.contains("https")) { httpReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC")); session.close(true); @@ -1180,7 +1180,7 @@ parsePorts(Config const& config, std::ostream& log) else { auto const count = std::count_if(result.cbegin(), result.cend(), [](Port const& p) { - return p.protocol.count("peer") != 0; + return p.protocol.contains("peer"); }); if (count > 1) @@ -1203,12 +1203,12 @@ setupClient(ServerHandler::Setup& setup) decltype(setup.ports)::const_iterator iter; for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter) { - if (iter->protocol.count("http") > 0 || iter->protocol.count("https") > 0) + if (iter->protocol.contains("http") || iter->protocol.contains("https")) break; } if (iter == setup.ports.cend()) return; - setup.client.secure = iter->protocol.count("https") > 0; + setup.client.secure = iter->protocol.contains("https"); if (beast::IP::isUnspecified(iter->ip)) { // VFALCO HACK! to make localhost work @@ -1230,7 +1230,7 @@ static void setupOverlay(ServerHandler::Setup& setup) { auto const iter = std::ranges::find_if( - setup.ports, [](Port const& port) { return port.protocol.count("peer") != 0; }); + setup.ports, [](Port const& port) { return port.protocol.contains("peer"); }); if (iter == setup.ports.cend()) { setup.overlay = {}; diff --git a/src/xrpld/shamap/NodeFamily.cpp b/src/xrpld/shamap/NodeFamily.cpp index a1668e80b20..0179f4253c7 100644 --- a/src/xrpld/shamap/NodeFamily.cpp +++ b/src/xrpld/shamap/NodeFamily.cpp @@ -3,13 +3,13 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include diff --git a/src/xrpld/shamap/NodeFamily.h b/src/xrpld/shamap/NodeFamily.h index e0655292a02..dfd6e2e276c 100644 --- a/src/xrpld/shamap/NodeFamily.h +++ b/src/xrpld/shamap/NodeFamily.h @@ -1,7 +1,6 @@ #pragma once -#include - +#include #include #include From 3bb6904946542d1e5a858a3b2ac31687f7686049 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Thu, 16 Jul 2026 12:45:46 -0400 Subject: [PATCH 6/8] feat: Dump metric values after test run --- src/libxrpl/tx/wasm/WasmVM.cpp | 4 +-- src/test/app/TestHostFunctions.h | 43 ++++++++++++++++++++++++++++++-- src/test/app/Wasm_test.cpp | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/libxrpl/tx/wasm/WasmVM.cpp b/src/libxrpl/tx/wasm/WasmVM.cpp index 2ab277e49b7..22cdbfa5afd 100644 --- a/src/libxrpl/tx/wasm/WasmVM.cpp +++ b/src/libxrpl/tx/wasm/WasmVM.cpp @@ -134,7 +134,7 @@ runEscrowWasm( hfs.executionTimeEvent("runEscrowWasm") .notify( - std::chrono::duration_cast( + std::chrono::duration_cast( std::chrono::steady_clock::now() - start)); if (!ret) @@ -169,7 +169,7 @@ preflightEscrowWasm( hfs.executionTimeEvent("preflightEscrowWasm") .notify( - std::chrono::duration_cast( + std::chrono::duration_cast( std::chrono::steady_clock::now() - start)); return ret; diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h index 80e0abae144..98be91ba678 100644 --- a/src/test/app/TestHostFunctions.h +++ b/src/test/app/TestHostFunctions.h @@ -13,27 +13,66 @@ #include +#include +#include #include #include #include #include +#include namespace xrpl::test { /** - * Lets a test assert that the WASM execution-timing path fired (and inspect - * the recorded duration) without needing a StatsD sink or a full Collector. + * Lets a test assert that the WASM execution-timing path fired and inspect the + * recorded durations, without needing a StatsD sink or a full Collector. */ struct RecordingEventImpl : public beast::insight::EventImpl { std::size_t count = 0; value_type last{}; + value_type total{}; + value_type min{value_type::max()}; + value_type max{value_type::min()}; + std::vector samples; + + ~RecordingEventImpl() override + { + std::cout << "Mean (ns): " << meanNs() << "\n"; + } void notify(value_type const& value) override { ++count; last = value; + total += value; + min = std::min(min, value); + max = std::max(max, value); + samples.push_back(value); + } + + [[nodiscard]] double + meanNs() const + { + return count != 0 ? static_cast(total.count()) / static_cast(count) : 0.0; + } + + [[nodiscard]] value_type + percentile(double p) const + { + if (samples.empty()) + { + return value_type{}; + } + auto sorted = samples; + std::ranges::sort(sorted); + auto rank = static_cast((p / 100.0) * static_cast(sorted.size())); + if (rank >= sorted.size()) + { + rank = sorted.size() - 1; + } + return sorted[rank]; } }; diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index e39bb98769e..0837eedaadf 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1588,7 +1588,7 @@ struct Wasm_test : public beast::unit_test::Suite auto mock = HostFunctions{env.journal}; auto hfns = TestHostFunctions{env, 0}; - perf(hfns, kRuns, [&] { return !preflightEscrowWasm(wasm, mock, escrowFunctionName); }); + perf(hfns, kRuns, [&] { return !preflightEscrowWasm(wasm, hfns, escrowFunctionName); }); } void From b9df9c9c45307d4aeac943cb3fb375c97ad1c037 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Thu, 16 Jul 2026 12:57:10 -0400 Subject: [PATCH 7/8] feat: Dump metric values after test run --- src/libxrpl/tx/wasm/WasmVM.cpp | 4 ++-- src/test/app/TestHostFunctions.h | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libxrpl/tx/wasm/WasmVM.cpp b/src/libxrpl/tx/wasm/WasmVM.cpp index 22cdbfa5afd..2ab277e49b7 100644 --- a/src/libxrpl/tx/wasm/WasmVM.cpp +++ b/src/libxrpl/tx/wasm/WasmVM.cpp @@ -134,7 +134,7 @@ runEscrowWasm( hfs.executionTimeEvent("runEscrowWasm") .notify( - std::chrono::duration_cast( + std::chrono::duration_cast( std::chrono::steady_clock::now() - start)); if (!ret) @@ -169,7 +169,7 @@ preflightEscrowWasm( hfs.executionTimeEvent("preflightEscrowWasm") .notify( - std::chrono::duration_cast( + std::chrono::duration_cast( std::chrono::steady_clock::now() - start)); return ret; diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h index 98be91ba678..9bac56e3cce 100644 --- a/src/test/app/TestHostFunctions.h +++ b/src/test/app/TestHostFunctions.h @@ -38,7 +38,10 @@ struct RecordingEventImpl : public beast::insight::EventImpl ~RecordingEventImpl() override { - std::cout << "Mean (ns): " << meanNs() << "\n"; + if (count > 0) + { + std::cout << "Mean (ms): " << meanMs() << "\n"; + } } void @@ -53,7 +56,7 @@ struct RecordingEventImpl : public beast::insight::EventImpl } [[nodiscard]] double - meanNs() const + meanMs() const { return count != 0 ? static_cast(total.count()) / static_cast(count) : 0.0; } From d1d3e3c72ebce8d97b9df3a146d7949aaf2b2fb5 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Thu, 16 Jul 2026 17:14:45 -0400 Subject: [PATCH 8/8] feat: Run levelization --- .github/scripts/levelization/results/loops.txt | 2 +- .github/scripts/levelization/results/ordering.txt | 1 + src/test/app/Wasm_test.cpp | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/levelization/results/loops.txt b/.github/scripts/levelization/results/loops.txt index fb449441e3f..8c34156bd23 100644 --- a/.github/scripts/levelization/results/loops.txt +++ b/.github/scripts/levelization/results/loops.txt @@ -14,7 +14,7 @@ Loop: xrpld.app xrpld.rpc xrpld.rpc > xrpld.app Loop: xrpld.app xrpld.shamap - xrpld.shamap > xrpld.app + xrpld.shamap ~= xrpld.app Loop: xrpld.overlay xrpld.rpc xrpld.rpc ~= xrpld.overlay diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index c2000d17681..18bd28135cd 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -294,6 +294,7 @@ xrpld.rpc > xrpl.server xrpld.rpc > xrpl.shamap xrpld.rpc > xrpl.tx xrpld.shamap > xrpl.basics +xrpld.shamap > xrpl.core xrpld.shamap > xrpld.core xrpld.shamap > xrpl.protocol xrpld.shamap > xrpl.shamap diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index 0837eedaadf..200e7c212d6 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1585,7 +1585,6 @@ struct Wasm_test : public beast::unit_test::Suite auto const wasm = hexToBytes(kAllHostFunctionsWasmHex); Env env{*this}; - auto mock = HostFunctions{env.journal}; auto hfns = TestHostFunctions{env, 0}; perf(hfns, kRuns, [&] { return !preflightEscrowWasm(wasm, hfns, escrowFunctionName); });