diff --git a/tools/fake_dragonxd/README.md b/tools/fake_dragonxd/README.md index d1a8100..7dd1688 100644 --- a/tools/fake_dragonxd/README.md +++ b/tools/fake_dragonxd/README.md @@ -14,7 +14,7 @@ What it copies from the real daemon (DragonX v1.3.0): command line or `DRAGONX.conf`). When the queue is full the request gets an immediate plain-text `HTTP 500 "Work queue depth exceeded"`. A request whose client timed out **keeps its slot** until a worker reaches it. -- **cs_main.** One global lock. Every method except `stop` and `help` takes it. A "witness rebuild" +- **cs_main.** One global lock. Every method except `stop`, `help` and `getrescaninfo` (lock-free in dragonxd too) takes it. A "witness rebuild" holds it for `FAKE_REBUILD_SECS` and logs the real markers: - `Reading blocks for witness rebuild: / ` (Phase 1, abortable); - nothing during Phase 2, which ignores shutdown; @@ -53,6 +53,7 @@ Everything that matters for a test is appended to `/fake_dragonxd.event | `sigterm` | SIGTERM/SIGINT received (POSIX) | | `http-interrupted`, `shutdown-begin` | shutdown progress | | `stats ...` | counters, every 30 s when they change | +| `methods ...` | the 10 most-requested JSON-RPC methods with counts (with `stats`, and at exit): what the GUI is polling | | `clean-exit ...` | **graceful exit**, with final counters. If this is missing, the process was killed. | ## Build diff --git a/tools/fake_dragonxd/fake_dragonxd.cpp b/tools/fake_dragonxd/fake_dragonxd.cpp index 6f917aa..ce24e84 100644 --- a/tools/fake_dragonxd/fake_dragonxd.cpp +++ b/tools/fake_dragonxd/fake_dragonxd.cpp @@ -7,7 +7,7 @@ // exceeded" before any handler runs, and a request whose client gave up keeps its slot until a // worker reaches it (libevent has no close callback); // * a global cs_main that a "witness rebuild" holds for minutes, logging the real markers. Every -// method except the lock-free `stop` / `help` takes it; +// method except the lock-free `stop` / `help` / `getrescaninfo` takes it; // * dragonxd's shutdown: `stop` (or SIGTERM/SIGINT on POSIX; Windows has no signal channel, as in // v1.3.0) unbinds the listener at once, answers new requests on open connections with 503, then // waits for the rebuild (Phase 2 is not abortable) before "Shutdown: In progress..." -> flush -> @@ -330,6 +330,23 @@ struct Stats { Stats g_stats; +// Requests per JSON-RPC method, for "what is the GUI polling" (the `methods` event). +std::mutex g_methodsMx; +std::map g_methodCounts; + +std::string topMethodsLine(size_t n) +{ + std::vector> v; + { + std::lock_guard lk(g_methodsMx); + for (const auto& kv : g_methodCounts) v.push_back({kv.second, kv.first}); + } + std::sort(v.rbegin(), v.rend()); + std::string out; + for (size_t i = 0; i < v.size() && i < n; ++i) out += (i ? " " : "") + v[i].second + "=" + std::to_string(v[i].first); + return out; +} + bool raiseMax(std::atomic& max, int value) { int cur = max.load(); @@ -541,11 +558,16 @@ json execOne(const json& req, int& status) if (!req.is_object() || !req.contains("method") || !req["method"].is_string()) return fail(-32600, "Missing method"); const std::string m = req["method"].get(); + { + std::lock_guard lk(g_methodsMx); + ++g_methodCounts[m]; + } json params = req.contains("params") ? req["params"] : json::array(); if (!params.is_array()) params = json::array(); status = 200; - const bool lockFree = (m == "stop" || m == "help"); + // Like dragonxd: stop and help take no lock, nor does getrescaninfo (it only reads wallet fields). + const bool lockFree = (m == "stop" || m == "help" || m == "getrescaninfo"); if (!lockFree && !cannedReply(m, params).known) return fail(-32601, "Method not found"); if (g_warmup.load() && !allowedInWarmup(m)) return fail(-28, warmupStatus()); @@ -561,7 +583,9 @@ json execOne(const json& req, int& status) } Canned c; - { + if (lockFree) { + c = cannedReply(m, params); + } else { std::lock_guard lk(g_csMain); c = cannedReply(m, params); if (m == "setgenerate") g_minerOn = !params.empty() && params[0].is_boolean() && params[0].get(); @@ -1264,7 +1288,10 @@ int main(int argc, char** argv) if (Clock::now() - lastStats >= 30s) { lastStats = Clock::now(); const std::string s = statsLine(); - if (s != lastStatsLine) event("stats", s); + if (s != lastStatsLine) { + event("stats", s); + event("methods", topMethodsLine(10)); + } lastStatsLine = s; } } @@ -1304,6 +1331,7 @@ int main(int argc, char** argv) fs::remove(cookieFile, ec2); } std::this_thread::sleep_for(200ms); // let connection threads flush their last replies + event("methods", topMethodsLine(10)); event("clean-exit", statsLine()); exitNow(0); }