diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,6 @@ This release includes the following features and fixes: + +- The RPC `getrpcinfo` returns runtime details of the RPC server. At the moment + it returns the active commands and the corresponding execution time. diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -35,6 +35,32 @@ /* Map of name to timer. */ static std::map> deadlineTimers; +struct RPCCommandExecutionInfo { + std::string method; + int64_t start; +}; + +struct RPCServerInfo { + Mutex mutex; + std::list active_commands GUARDED_BY(mutex); +}; + +static RPCServerInfo g_rpc_server_info; + +struct RPCCommandExecution { + std::list::iterator it; + explicit RPCCommandExecution(const std::string &method) { + LOCK(g_rpc_server_info.mutex); + it = g_rpc_server_info.active_commands.insert( + g_rpc_server_info.active_commands.cend(), + {method, GetTimeMicros()}); + } + ~RPCCommandExecution() { + LOCK(g_rpc_server_info.mutex); + g_rpc_server_info.active_commands.erase(it); + } +}; + UniValue RPCServer::ExecuteCommand(Config &config, const JSONRPCRequest &request) const { // Return immediately if in warmup @@ -75,7 +101,6 @@ static struct CRPCSignals { boost::signals2::signal Started; boost::signals2::signal Stopped; - boost::signals2::signal PreCommand; } g_rpcSignals; void RPCServerSignals::OnStarted(std::function slot) { @@ -319,6 +344,29 @@ return GetTime() - GetStartupTime(); } +static UniValue getrpcinfo(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error("getrpcinfo\n" + "\nReturns details of the RPC server.\n"); + } + + LOCK(g_rpc_server_info.mutex); + UniValue active_commands(UniValue::VARR); + for (const RPCCommandExecutionInfo &info : + g_rpc_server_info.active_commands) { + UniValue entry(UniValue::VOBJ); + entry.pushKV("method", info.method); + entry.pushKV("duration", GetTimeMicros() - info.start); + active_commands.push_back(entry); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("active_commands", active_commands); + + return result; +} + /** * Call Table */ @@ -327,6 +375,7 @@ // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- /* Overall control/query calls */ + { "control", "getrpcinfo", getrpcinfo, {} }, { "control", "help", help, {"command"} }, { "control", "stop", stop, {"wait"} }, { "control", "uptime", uptime, {} }, @@ -521,9 +570,8 @@ throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); } - g_rpcSignals.PreCommand(*pcmd); - try { + RPCCommandExecution execution(request.strMethod); // Execute, convert arguments to array if necessary if (request.params.isObject()) { return pcmd->call(config, diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -5,7 +5,7 @@ """Tests some generic aspects of the RPC interface.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import assert_equal, assert_greater_than_or_equal class RPCInterfaceTest(BitcoinTestFramework): @@ -13,6 +13,16 @@ self.num_nodes = 1 self.setup_clean_chain = True + def test_getrpcinfo(self): + self.log.info("Testing getrpcinfo...") + + info = self.nodes[0].getrpcinfo() + assert_equal(len(info['active_commands']), 1) + + command = info['active_commands'][0] + assert_equal(command['method'], 'getrpcinfo') + assert_greater_than_or_equal(command['duration'], 0) + def test_batch_request(self): self.log.info("Testing basic JSON-RPC batch request...") @@ -40,6 +50,7 @@ assert result_by_id[3]['result'] is not None def run_test(self): + self.test_getrpcinfo() self.test_batch_request()