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/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/src/xrpld/app/main/CollectorManager.h b/include/xrpl/core/CollectorManager.h similarity index 100% rename from src/xrpld/app/main/CollectorManager.h rename to include/xrpl/core/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/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..9bac56e3cce 100644 --- a/src/test/app/TestHostFunctions.h +++ b/src/test/app/TestHostFunctions.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -11,13 +13,72 @@ #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 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 + { + if (count > 0) + { + std::cout << "Mean (ms): " << meanMs() << "\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 + meanMs() 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]; + } +}; + struct TestLedgerDataProvider : public HostFunctions { jtx::Env& env; @@ -54,6 +115,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 +138,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 537b8b2330e..200e7c212d6 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -1544,11 +1544,60 @@ struct Wasm_test : public beast::unit_test::Suite } } + template + void + perf(TestHostFunctions& hfs, size_t runs, Functor&& f) + { + for (auto i = size_t{}; i < runs; ++i) + { + auto result = f(); + BEAST_EXPECT(result); + } + BEAST_EXPECT(hfs.execTimeEvent->count == runs); + } + + 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(hfns, 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 hfns = TestHostFunctions{env, 0}; + + perf(hfns, kRuns, [&] { return !preflightEscrowWasm(wasm, hfns, escrowFunctionName); }); + } + void run() override { using namespace test::jtx; + perfEscrowFinish(); + perfEscrowCreate(); + testGetDataHelperFunctions(); testWasmLib(); testBadWasm(); 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 6cdbca8d8ac..b9bd3cc2a53 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -1,4 +1,5 @@ -#include + +#include #include #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