From 0978d7d050913b444bb538c3e4f62b7919f3e947 Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Wed, 6 Dec 2023 13:55:06 +0100 Subject: [PATCH] rpc: make it possible to aggregate the result in getnetmsgstats Aggregation can be by either one of direction, network, connection type or message type. For example if the following ``` { "ipv4": { "ping": 3 }, "ipv6": { "ping": 4 } } ``` is aggregated by network, then the result will be ``` { "ping": 7 } ``` --- src/rpc/client.cpp | 1 + src/rpc/net.cpp | 112 +++++++++++++++++++++++++++++++++------------ 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1b711e3c5b1..fd85dcca756 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -253,6 +253,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "verifychain", 1, "nblocks" }, { "getblockstats", 0, "hash_or_height" }, { "getblockstats", 1, "stats" }, + { "getnetmsgstats", 0, "aggregate_by"}, { "pruneblockchain", 0, "height" }, { "keypoolrefill", 0, "newsize" }, { "getrawmempool", 0, "verbose" }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 5ac37e472db..cef465103bd 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -548,11 +548,44 @@ static RPCHelpMan getaddednodeinfo() namespace net_stats { +namespace aggregate_dimensions { +static std::string DIRECTION{"direction"}; +static std::string NETWORK{"network"}; +static std::string CONNECTION_TYPE{"connection_type"}; +static std::string MESSAGE_TYPE{"message_type"}; +}; + UniValue CreateJSON(const RPCHelpMan&, const JSONRPCRequest& request) { const NodeContext& node = EnsureAnyNodeContext(request.context); const CConnman& connman = EnsureConnman(node); + // Used for a quick check if a string is in request.params[0] which is + // supposed to be a JSON array, e.g. ["direction", "network"]. + std::unordered_set aggregate_by; + if (request.params[0].isArray()) { + const UniValue& arr{request.params[0].get_array()}; + for (size_t i = 0; i < arr.size(); ++i) { + const auto& agg{arr[i].get_str()}; + if (agg != aggregate_dimensions::DIRECTION && + agg != aggregate_dimensions::NETWORK && + agg != aggregate_dimensions::CONNECTION_TYPE && + agg != aggregate_dimensions::MESSAGE_TYPE) { + throw JSONRPCError( + RPC_INVALID_PARAMS, + strprintf( + R"(Unrecognized aggregation parameter: "%s". The array should consist of zero or more of "%s", "%s", "%s", "%s".)", + agg, + aggregate_dimensions::DIRECTION, + aggregate_dimensions::NETWORK, + aggregate_dimensions::CONNECTION_TYPE, + aggregate_dimensions::MESSAGE_TYPE)); + } + aggregate_by.insert(agg); + } + } + + // The keys might as well be an empty string (if aggregating by that dimension). std::unordered_map< std::string, // "sent" or "recv" std::unordered_map>>> result_map; - connman.GetNetStats().ForEach([&result_map](NetStats::Direction dir, - Network net, - ConnectionType con, - const std::string& msg, - const NetStats::BytesAndCount& data) { - const std::string dir_str{dir == NetStats::SENT ? "sent" : "recv"}; - const std::string net_str{GetNetworkName(net)}; - const std::string con_str{ConnectionTypeAsString(con)}; + connman.GetNetStats().ForEach([&aggregate_by, &result_map](NetStats::Direction dir, + Network net, + ConnectionType con, + const std::string& msg, + const NetStats::BytesAndCount& data) { + const std::string dir_str{aggregate_by.contains(aggregate_dimensions::DIRECTION) ? "" : dir == NetStats::SENT ? "sent" : "recv"}; + const std::string net_str{aggregate_by.contains(aggregate_dimensions::NETWORK) ? "" : GetNetworkName(net)}; + const std::string con_str{aggregate_by.contains(aggregate_dimensions::CONNECTION_TYPE) ? "" : ConnectionTypeAsString(con)}; + const std::string msg_str{aggregate_by.contains(aggregate_dimensions::MESSAGE_TYPE) ? "" : msg}; - result_map[dir_str][net_str][con_str][msg] += data; + result_map[dir_str][net_str][con_str][msg_str] += data; }); auto Add = [](UniValue& target, const std::string& key, const UniValue& val) { if (val.empty()) { return; } + if (key.empty()) { + target = val; + return; + } target.pushKV(key, val); }; @@ -614,8 +652,29 @@ static RPCHelpMan getnetmsgstats() { return RPCHelpMan{ "getnetmsgstats", - "\nReturns the messages count and total number of bytes for network traffic.\n", - {}, + "\nReturns the messages count and total number of bytes for network traffic.\n" + "Results may optionally be aggregated.\n", + {RPCArg{ + "aggregate_by", + RPCArg::Type::ARR, + RPCArg::DefaultHint{"empty, no aggregation"}, + "An array of keywords for aggregating the results.", + {RPCArg{"direction", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "Aggregate by direction and don't show direction in the result."}, + RPCArg{"network", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "Aggregate by network and don't show network in the result."}, + RPCArg{"connection_type", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "Aggregate by connection type and don't show connection type in the result."}, + RPCArg{"message_type", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "Aggregate by message type and don't show message type in the result."}}}}, {RPCResult{ RPCResult::Type::OBJ_DYN, "", @@ -638,29 +697,24 @@ static RPCHelpMan getnetmsgstats() "inbound", true, "The connection type over which the traffic occurred.", - {RPCResult{RPCResult::Type::OBJ, - "verack", - true, - "Type of the messages transferred.", - {RPCResult{RPCResult::Type::NUM, - "bytes", - false, - "Total number of bytes.", - {}, - true}, - RPCResult{RPCResult::Type::NUM, - "count", - false, - "Total number of messages.", - {}, - true}}, - true}}, + {RPCResult{ + RPCResult::Type::OBJ, + "verack", + true, + "Type of the messages transferred.", + { + RPCResult{RPCResult::Type::NUM, "bytes", false, "Total number of bytes.", {}, true}, + RPCResult{RPCResult::Type::NUM, "count", false, "Total number of messages.", {}, true} + }, + true}}, true}}, true}}, true}}, true}}, RPCExamples{HelpExampleCli("getnetmsgstats", "") + - HelpExampleRpc("getnetmsgstats", "")}, + HelpExampleCli("getnetmsgstats", R"('["network", "message_type"]')") + + HelpExampleRpc("getnetmsgstats", "") + + HelpExampleRpc("getnetmsgstats", R"(["network", "message_type"])")}, net_stats::CreateJSON}; }