Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport RPCStats to master #1260

Merged
merged 2 commits into from
May 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ DEFI_CORE_H = \
rpc/request.h \
rpc/server.h \
rpc/util.h \
rpc/stats.h \
scheduler.h \
script/descriptor.h \
script/keyorigin.h \
Expand Down Expand Up @@ -603,6 +604,7 @@ libdefi_common_a_SOURCES = \
psbt.cpp \
rpc/rawtransaction_util.cpp \
rpc/util.cpp \
rpc/stats.cpp \
scheduler.cpp \
script/descriptor.cpp \
script/sign.cpp \
Expand Down
15 changes: 12 additions & 3 deletions src/httprpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <httpserver.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
#include <rpc/stats.h>
#include <sync.h>
#include <ui_interface.h>
#include <util/strencodings.h>
Expand Down Expand Up @@ -148,7 +149,7 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna

static bool CorsHandler(HTTPRequest *req) {
auto host = corsOriginHost;
// If if it's empty assume cors is disallowed. Do nothing and proceed,
// If if it's empty assume cors is disallowed. Do nothing and proceed,
// with request as usual.
if (host.empty())
return false;
Expand All @@ -157,7 +158,7 @@ static bool CorsHandler(HTTPRequest *req) {
req->WriteHeader("Access-Control-Allow-Credentials", "true");
req->WriteHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
req->WriteHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");

// If it's a Cors preflight request, short-circuit and return. We have
// set what's needed already.
if (req->GetRequestMethod() == HTTPRequest::OPTIONS) {
Expand All @@ -171,8 +172,9 @@ static bool CorsHandler(HTTPRequest *req) {

static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
{
int64_t time = GetTimeMillis();
// Handle CORS
if (CorsHandler(req))
if (CorsHandler(req))
return true;

// JSONRPC handles only POST
Expand Down Expand Up @@ -230,6 +232,8 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)

req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strReply);

if (statsRPC.isActive()) statsRPC.add(jreq.strMethod, GetTimeMillis() - time, strReply.length());
} catch (const UniValue& objError) {
JSONErrorReply(req, objError, jreq.id);
return false;
Expand Down Expand Up @@ -279,6 +283,7 @@ bool StartHTTPRPC()
assert(eventBase);
httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(eventBase);
RPCSetTimerInterface(httpRPCTimerInterface.get());
if (statsRPC.isActive()) statsRPC.load();
return true;
}

Expand All @@ -298,4 +303,8 @@ void StopHTTPRPC()
RPCUnsetTimerInterface(httpRPCTimerInterface.get());
httpRPCTimerInterface.reset();
}

if (statsRPC.isActive()) statsRPC.save();
}

CRPCStats statsRPC;
4 changes: 4 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/register.h>
#include <rpc/stats.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <scheduler.h>
Expand Down Expand Up @@ -591,6 +592,7 @@ void SetupServerArgs()
gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcallowcors=<host>", "Allow CORS requests from the given host origin. Include scheme and port (eg: -rpcallowcors=http://127.0.0.1:5000)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcstats", strprintf("Log RPC stats. (default: %u)", DEFAULT_RPC_STATS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);

#if HAVE_DECL_DAEMON
gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
Expand Down Expand Up @@ -2066,5 +2068,7 @@ bool AppInitMain(InitInterfaces& interfaces)
));
}

if (!gArgs.GetBoolArg("-rpcstats", DEFAULT_RPC_STATS)) statsRPC.setActive(false);

return true;
}
3 changes: 3 additions & 0 deletions src/rpc/register.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class CRPCTable;
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
/** Register P2P networking RPC commands */
void RegisterNetRPCCommands(CRPCTable &tableRPC);
/** Register stats RPC commands */
void RegisterStatsRPCCommands(CRPCTable &tableRPC);
/** Register miscellaneous RPC commands */
void RegisterMiscRPCCommands(CRPCTable &tableRPC);
/** Register mining RPC commands */
Expand Down Expand Up @@ -45,6 +47,7 @@ static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
RegisterNetRPCCommands(t);
RegisterStatsRPCCommands(t);
RegisterMiscRPCCommands(t);
RegisterMiningRPCCommands(t);
RegisterRawTransactionRPCCommands(t);
Expand Down
203 changes: 203 additions & 0 deletions src/rpc/stats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include <rpc/stats.h>

#include <rpc/server.h>
#include <rpc/util.h>

UniValue RPCStats::toJSON() {
UniValue stats(UniValue::VOBJ),
latencyObj(UniValue::VOBJ),
payloadObj(UniValue::VOBJ),
historyArr(UniValue::VARR);

latencyObj.pushKV("min", latency.min);
latencyObj.pushKV("avg", latency.avg);
latencyObj.pushKV("max", latency.max);

payloadObj.pushKV("min", payload.min);
payloadObj.pushKV("avg", payload.avg);
payloadObj.pushKV("max", payload.max);

for (auto const &entry : history) {
UniValue historyObj(UniValue::VOBJ);
historyObj.pushKV("timestamp", entry.timestamp);
historyObj.pushKV("latency", entry.latency);
historyObj.pushKV("payload", entry.payload);
historyArr.push_back(historyObj);
}

stats.pushKV("name", name);
stats.pushKV("count", count);
stats.pushKV("lastUsedTime", lastUsedTime);
stats.pushKV("latency", latencyObj);
stats.pushKV("payload", payloadObj);
stats.pushKV("history", historyArr);
return stats;
}

RPCStats RPCStats::fromJSON(UniValue json) {
RPCStats stats;

stats.name = json["name"].get_str();
stats.lastUsedTime = json["lastUsedTime"].get_int64();
stats.count = json["count"].get_int64();
if (!json["latency"].isNull()) {
auto latencyObj = json["latency"].get_obj();
stats.latency = {
latencyObj["min"].get_int64(),
latencyObj["avg"].get_int64(),
latencyObj["max"].get_int64()
};
}

if (!json["payload"].isNull()) {
auto payloadObj = json["payload"].get_obj();
stats.payload = {
payloadObj["min"].get_int64(),
payloadObj["avg"].get_int64(),
payloadObj["max"].get_int64()
};
}

if (!json["history"].isNull()) {
auto historyArr = json["history"].get_array();
for (const auto &entry : historyArr.getValues()) {
auto historyObj = entry.get_obj();
StatHistoryEntry historyEntry {
historyObj["timestamp"].get_int64(),
historyObj["latency"].get_int64(),
historyObj["payload"].get_int64()
};
stats.history.push_back(historyEntry);
}
}
return stats;
}

void CRPCStats::add(const std::string& name, const int64_t latency, const int64_t payload)
{
auto stats = CRPCStats::get(name);
if (stats) {
stats->count++;
stats->lastUsedTime = GetSystemTimeInSeconds();
stats->latency = {
std::min(latency, stats->latency.min),
stats->latency.avg + (latency - stats->latency.avg) / stats->count,
std::max(latency, stats->latency.max)
};
stats->payload = {
std::min(payload, stats->payload.min),
stats->payload.avg + (payload - stats->payload.avg) / stats->count,
std::max(payload, stats->payload.max)
};
} else {
stats = { name, latency, payload };
}
stats->history.push_back({ stats->lastUsedTime, latency, payload });

CLockFreeGuard lock(lock_stats);
map[name] = *stats;
}

UniValue CRPCStats::toJSON() {
auto map = CRPCStats::getMap();

UniValue ret(UniValue::VARR);
for (auto &[_, stats] : map) {
ret.push_back(stats.toJSON());
}
return ret;
}

static UniValue getrpcstats(const JSONRPCRequest& request)
{
RPCHelpMan{"getrpcstats",
"\nGet RPC stats for selected command.\n",
{
{"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to get stats for."}
},
RPCResult{
" {\n"
" \"name\": (string) The RPC command name.\n"
" \"latency\": (json object) Min, max and average latency.\n"
" \"payload\": (json object) Min, max and average payload size in bytes.\n"
" \"count\": (numeric) The number of times this command as been used.\n"
" \"lastUsedTime\": (numeric) Last used time as timestamp.\n"
" \"history\": (json array) History of last 5 RPC calls.\n"
" [\n"
" {\n"
" \"timestamp\": (numeric)\n"
" \"latency\": (numeric)\n"
" \"payload\": (numeric)\n"
" }\n"
" ]\n"
"}"
},
RPCExamples{
HelpExampleCli("getrpcstats", "getblockcount") +
HelpExampleRpc("getrpcstats", "\"getblockcount\"")
},
}.Check(request);

if (!statsRPC.isActive()) {
throw JSONRPCError(RPC_INVALID_REQUEST, "Rpcstats is desactivated.");
}

auto command = request.params[0].get_str();
auto stats = statsRPC.get(command);
if (!stats) {
throw JSONRPCError(RPC_INVALID_PARAMS, "No stats for this command.");
}
return stats->toJSON();
}

static UniValue listrpcstats(const JSONRPCRequest& request)
{
RPCHelpMan{"listrpcstats",
"\nList used RPC commands.\n",
{},
RPCResult{
"[\n"
" {\n"
" \"name\": (string) The RPC command name.\n"
" \"latency\": (json object) Min, max and average latency.\n"
" \"payload\": (json object) Min, max and average payload size in bytes.\n"
" \"count\": (numeric) The number of times this command as been used.\n"
" \"lastUsedTime\": (numeric) Last used time as timestamp.\n"
" \"history\": (json array) History of last 5 RPC calls.\n"
" [\n"
" {\n"
" \"timestamp\": (numeric)\n"
" \"latency\": (numeric)\n"
" \"payload\": (numeric)\n"
" }\n"
" ]\n"
" }\n"
"]"
},
RPCExamples{
HelpExampleCli("listrpcstats", "") +
HelpExampleRpc("listrpcstats", "")
},
}.Check(request);

if (!statsRPC.isActive()) {
throw JSONRPCError(RPC_INVALID_REQUEST, "Rpcstats is desactivated.");
}

return statsRPC.toJSON();
}

// clang-format off
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "stats", "getrpcstats", &getrpcstats, {"command"} },
{ "stats", "listrpcstats", &listrpcstats, {} },
};
// clang-format on

void RegisterStatsRPCCommands(CRPCTable &t)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
Loading