wallet/rpc: time GetFilteredNotes walks and slow RPCs
GetFilteredNotes logged "GetFilteredNotes ignoreLocked=N" on every call. The balance RPCs run it on every GUI poll, so that line was log spam on a fast wallet, and on a slow one the gaps between its timestamps were the only timing there was. Those gaps also count lock waits and the previous RPC's own post-processing. It now logs one summary per walk: the filters, txs that passed the tx-level checks out of the wallet total, notes examined, notes skipped as spent, entries returned, and the walk time. The summary is always written when the walk takes >= 500ms (that long under cs_main stalls block connection) and otherwise only with -debug=bench. The line has no lock-wait figure: every caller already holds LOCK2(cs_main, cs_wallet) and CCriticalSection is recursive, so the inner LOCK2 never waits. Lock waits are measured where they happen instead. CRPCTable::execute logs "RPC <method> took <n>ms" under the same >= 500ms / -debug=bench rule. The per-locked-note line moves to -debug=zrpcunsafe, since it logs txid prefixes. No behaviour change otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -640,6 +640,23 @@ std::string JSONRPCExecBatch(const UniValue& vReq)
|
|||||||
return ret.write() + "\n";
|
return ret.write() + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Logs an RPC whose wall time reached 500ms, or every RPC under -debug=bench. That time includes
|
||||||
|
// waiting for cs_main / cs_wallet behind block connection or another RPC, which the callee's own
|
||||||
|
// timers can't see because they start once the locks are held.
|
||||||
|
class RPCCallTimer {
|
||||||
|
const std::string& strMethod;
|
||||||
|
const int64_t nTimeStart;
|
||||||
|
public:
|
||||||
|
explicit RPCCallTimer(const std::string& method) : strMethod(method), nTimeStart(GetTimeMicros()) {}
|
||||||
|
~RPCCallTimer() {
|
||||||
|
const int64_t nMs = (GetTimeMicros() - nTimeStart) / 1000;
|
||||||
|
if (nMs >= 500 || LogAcceptCategory("bench"))
|
||||||
|
LogPrintf("RPC %s took %dms\n", strMethod, nMs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const
|
UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const
|
||||||
{
|
{
|
||||||
// Find method
|
// Find method
|
||||||
@@ -674,6 +691,7 @@ UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Execute
|
// Execute
|
||||||
|
RPCCallTimer timer(strMethod);
|
||||||
return pcmd->actor(params, false, CPubKey());
|
return pcmd->actor(params, false, CPubKey());
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
|
|||||||
+18
-2
@@ -5751,7 +5751,11 @@ void CWallet::GetFilteredNotes(
|
|||||||
{
|
{
|
||||||
LOCK2(cs_main, cs_wallet);
|
LOCK2(cs_main, cs_wallet);
|
||||||
|
|
||||||
LogPrintf("%s ignoreLocked=%d\n", __func__, ignoreLocked);
|
// Every caller already holds LOCK2, so the lock above never waits and this times the walk alone.
|
||||||
|
// Time spent waiting for the locks shows up in the RPC's own "took" line (rpc/server.cpp).
|
||||||
|
const int64_t nTimeStart = GetTimeMicros();
|
||||||
|
const size_t nEntriesBefore = saplingEntries.size();
|
||||||
|
unsigned int nTxs = 0, nNotes = 0, nSpent = 0;
|
||||||
|
|
||||||
for (auto & p : mapWallet) {
|
for (auto & p : mapWallet) {
|
||||||
CWalletTx wtx = p.second;
|
CWalletTx wtx = p.second;
|
||||||
@@ -5776,10 +5780,12 @@ void CWallet::GetFilteredNotes(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
nTxs++;
|
||||||
|
|
||||||
for (auto & pair : wtx.mapSaplingNoteData) {
|
for (auto & pair : wtx.mapSaplingNoteData) {
|
||||||
SaplingOutPoint op = pair.first;
|
SaplingOutPoint op = pair.first;
|
||||||
SaplingNoteData nd = pair.second;
|
SaplingNoteData nd = pair.second;
|
||||||
|
nNotes++;
|
||||||
|
|
||||||
auto maybe_pt = SaplingNotePlaintext::decrypt(
|
auto maybe_pt = SaplingNotePlaintext::decrypt(
|
||||||
wtx.vShieldedOutput[op.n].encCiphertext,
|
wtx.vShieldedOutput[op.n].encCiphertext,
|
||||||
@@ -5799,6 +5805,7 @@ void CWallet::GetFilteredNotes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ignoreSpent && nd.nullifier && IsSaplingSpent(*nd.nullifier)) {
|
if (ignoreSpent && nd.nullifier && IsSaplingSpent(*nd.nullifier)) {
|
||||||
|
nSpent++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5815,7 +5822,7 @@ void CWallet::GetFilteredNotes(
|
|||||||
|
|
||||||
// skip locked notes
|
// skip locked notes
|
||||||
if (ignoreLocked && IsLockedNote(op)) {
|
if (ignoreLocked && IsLockedNote(op)) {
|
||||||
LogPrintf("%s: skipping locked note %s\n", __func__, op.hash.ToString().substr(0,10).c_str());
|
LogPrint("zrpcunsafe", "%s: skipping locked note %s\n", __func__, op.hash.ToString().substr(0,10).c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5824,6 +5831,15 @@ void CWallet::GetFilteredNotes(
|
|||||||
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A slow walk holds cs_main long enough to stall block connection, so it is always logged; a
|
||||||
|
// fast one only with -debug=bench (the balance RPCs run it on every GUI poll).
|
||||||
|
const int64_t nWalkMs = (GetTimeMicros() - nTimeStart) / 1000;
|
||||||
|
if (nWalkMs >= 500 || LogAcceptCategory("bench")) {
|
||||||
|
LogPrintf("%s: minDepth=%d ignoreSpent=%d requireSpendingKey=%d ignoreLocked=%d txs=%u/%u notes=%u spent=%u entries=%u walk=%dms\n",
|
||||||
|
__func__, minDepth, ignoreSpent, requireSpendingKey, ignoreLocked, nTxs, (unsigned int)mapWallet.size(),
|
||||||
|
nNotes, nSpent, (unsigned int)(saplingEntries.size() - nEntriesBefore), nWalkMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user