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
log bytes recv/sent per command #6589
Conversation
@@ -2171,7 +2174,7 @@ void CNode::AbortMessage() UNLOCK_FUNCTION(cs_vSend) | |||
LogPrint("net", "(aborted)\n"); | |||
} | |||
|
|||
void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend) | |||
void CNode::EndMessage(const char* pszCommand) UNLOCK_FUNCTION(cs_vSend) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to use const std::string& ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CNode::BeginMessage()
also uses const char*
(https://github.com/bitcoin/bitcoin/blob/master/src/net.h#L499). EndMessage()
is always called over PushMessage(const char* pszCommand, const T1& a1, ...)
. Not sure if it would make sense to cast/transform to a std::string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I agree with @sipa that I prefer std::string, but yes for that to have any effect whatsoever that'd require switching that entire call chain to std::string
and that is out of the scope of this pull.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, we are already changing EndMessage()
here, so it could be done in one step, together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's to invasive for a little logging feature. It would basically require to change all PushMessage(...)
then BeginMessage()
and EndMessage()
function. An either conversion of CMessageHeader (that it accepts std::string
) or some conversions to c-strings in the mentioned functions.
IMO, it's not worth changing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
Concept ACK, I think this is useful. |
cb3efd9
to
7b38289
Compare
Thanks @sipa for reviewing! |
I haven't checked the code for this, but we should make sure this only
accounts for incoming messages of a known type. Otherwise you introduce a
memory DoS attack by allowing the sender to construct an arbitrarily-sized
map.
|
@sipa: good point! Will address it soon. |
These maps will need locking Edit: I take that back. It looks like the cs_vNodes in CopyNodeStats is enough. Might want to make CNode::mapSendBytesPerCmd / CNode::mapRecvBytesPerCmd private though, so nobody's tempted to use them directly. |
7283ebf
to
2489449
Compare
@theuni: Right. I was aware of the locking and did come to the conclusion that both new maps are protected by Moved the maps to the private section. |
Added memory DOS prevention by filtering valid commands. |
//store received bytes per command | ||
//to prevent a memory DOS, only allow valid commands | ||
std::string command(msg.hdr.pchCommand); | ||
for (unsigned int i = 0; i < sizeof(logAllowIncommingCmds)/sizeof(logAllowIncommingCmds[0]); i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a map lookup, can we just initialize the map once with entries for each valid string, and then do a find() here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Would be possible. I somehow searched for a solution that doesn't require initialization. Do you think the map.find()
would be faster or just less code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly I was thinking less code/easier to read (though it would be faster too, since we have to do a find() anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C++11 to the rescue:
const static std::set<std::string> logAllowIncommingCmds = {
"version", "verack", "addr", "inv", "getdata",
"getblocks", "getheaders", "tx", "headers", "block",
"getaddr", "mempool", "ping", "pong", "alert",
"filterload", "filteradd", "filterclear", "reject"
};
if (logAllowIncomingCmds.count(command))
mapRecvBytesPerCmd[command] += msg.hdr.nMessageSize;
ut ACK - this actually recreates a change I wrote a long time ago (first version of getpeerinfo did this) |
concept ACK edit: is it worth persisting this to storage? |
e332924
to
d547377
Compare
IMO @dcousens: not sure if disk persistence would make sense here. I'd like to extend this up to the GUI layer to have a better network/bandwidth graph that could also generate and show data when it's not opened, though, this PR does not include time based data logging. |
d547377
to
6fef53a
Compare
Passed Travis now. |
Going to test this |
//store received bytes per command | ||
//to prevent a memory DOS, only allow valid commands | ||
std::string command(msg.hdr.pchCommand); | ||
for (unsigned int i = 0; i < sizeof(logAllowIncomingCmds)/sizeof(logAllowIncomingCmds[0]); i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought of - what I think is an even better solution - as it doesn't require recourse to anything besides the map itself: prepopulate the mapRecvBytesPerCmd
. At initialization:
for (unsigned int i = 0; i < sizeof(logAllowIncomingCmds)/sizeof(logAllowIncomingCmds[0]); i++)
mapRecvBytesPerCmd[logAllowIncomingCmds[i]] = 0;
Then only increase if the item exists, e.g.
mapCmdSize_t::iterator i = mapRecvBytesPerCmd.find(msg.hdr.pchCommand);
if (i != mapRecvBytesPerCmd.end())
i->second += msg.hdr.nMessageSize;
@laanwj: I like your map.find approach. |
|
||
UniValue recvPerCmd(UniValue::VOBJ); | ||
BOOST_FOREACH( const mapCmdSize::value_type &i, stats.mapRecvBytesPerCmd) { | ||
recvPerCmd.push_back(Pair(i.first, i.second)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want we could hide the entries with i.second==0
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also though about that, but I think the null entries are better instead of hiding. It result in more controllable parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would this result in more controllable parsing? The zeros don't mean anything. The only reason those entries are there in the first place is our 'ignore unknown command' implementation detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, to be consistent, if you really want an overview of all possible messages, we want zero entries for mapSendBytesPerCmd
too (this will require an array of all messages we can potentially send).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I though if someone writes a statistics fronted that uses the RPC as data-source it would complicate things (a little bit) because you don't have a full list of available commands at the beginning. But no strong opinion. I think the drawback right now is a relatively big response.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK i agree. If you want to know eg. the set of commands that is not considered *other*
. OK I don't care deeply about this, fine to leave it as-is, but I expect more people to comment on this. Empty rows just look unnecessary, and it's repeated for every peer.
Minimally-tested ACK, I like this functionality a lot |
Speaking of which, why is
|
Correct. Right now only the message size counts... so your right. This needs to be fixed, otherwise we are not really counting net bytes. Working on it. |
ACK |
obj.push_back(Pair("bytessent_per_msg", sendPerMsgCmd)); | ||
|
||
UniValue recvPerCmd(UniValue::VOBJ); | ||
BOOST_FOREACH( const mapMsgCmdSize::value_type &i, stats.mapRecvBytesPerMsgCmd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I believe these should read "BOOST_FOREACH(const mapMsgSize..." (No space between "(" and "const")
Fixed minor code style issues (three unnecessary spaces). |
8208741
to
a6001a9
Compare
Will merge after squash and travis OK |
a6001a9
to
ca188c6
Compare
Squashed. |
ca188c6 log bytes recv/sent per command (Jonas Schnelli)
Looks like these were forgotten in bitcoin#6589.
Looks like these were forgotten in bitcoin#6589.
@@ -2480,6 +2503,9 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend) | |||
unsigned int nSize = ssSend.size() - CMessageHeader::HEADER_SIZE; | |||
WriteLE32((uint8_t*)&ssSend[CMessageHeader::MESSAGE_SIZE_OFFSET], nSize); | |||
|
|||
//log total amount of bytes per command | |||
mapSendBytesPerMsgCmd[std::string(pszCommand)] += nSize + CMessageHeader::HEADER_SIZE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not put ssSend.size() into a variable and use that?
Is anyone using this functionality? If so, can uses be documented somewhere? |
@rebroad I think the goal was to find out what msg consumes the most bytes. |
Looks like these were forgotten in bitcoin#6589.
…itcoin#7181: enable per-command byte counters in `CNode` (#1496) * log bytes recv/sent per command * net: Account for `sendheaders` `verack` messages Looks like these were forgotten in bitcoin#6589. * Backport remaining part of Bitcoin PR bitcoin#7181. Most of this PR is already merged, but a small part remaining that makes per-command byte counts in CNode working. Signed-off-by: Oleg Girko <ol@infoserver.lv>
Looks like these were forgotten in bitcoin#6589.
30d5c66 net: correct addrman logging (Fuzzbawls) 8a2b7fe Don't send layer2 messages to peers that haven't completed the handshake (Fuzzbawls) dc10100 [bugfix] Making tier two thread interruptable. (furszy) 2ae76aa Move CNode::addrLocal access behind locked accessors (Fuzzbawls) 470482f Move CNode::addrName accesses behind locked accessors (Fuzzbawls) 35365e1 Move [clean|str]SubVer writes/copyStats into a lock (Fuzzbawls) d816a86 Make nServices atomic (Matt Corallo) 8a66add Make nStartingHeight atomic (Matt Corallo) 567c9b5 Avoid copying CNodeStats to make helgrind OK with buggy std::string (Matt Corallo) aea5211 Make nTimeConnected const in CNode (Matt Corallo) cf46680 net: fix a few races. (Fuzzbawls) c916fcf net: add a lock around hSocket (Cory Fields) cc8a93c net: rearrange so that socket accesses can be grouped together (Cory Fields) 6f731dc Do not add to vNodes until fOneShot/fFeeler/fAddNode have been set (Matt Corallo) 07c8d33 Ensure cs_vNodes is held when using the return value from FindNode (Matt Corallo) 110a44b Delete some unused (and broken) functions in CConnman (Matt Corallo) 08a12e0 net: log an error rather than asserting if send version is misused (Cory Fields) cd8b82c net: Disallow sending messages until the version handshake is complete (Fuzzbawls) 54b454b net: don't run callbacks on nodes that haven't completed the version handshake (Cory Fields) 2be6877 net: deserialize the entire version message locally (Fuzzbawls) 444f599 Dont deserialize nVersion into CNode (Fuzzbawls) f30f10e net: remove cs_vRecvMsg (Fuzzbawls) 5812f9e net: add a flag to indicate when a node's send buffer is full (Fuzzbawls) 5ec4db2 net: Hardcode protocol sizes and use fixed-size types (Wladimir J. van der Laan) de87ea6 net: Consistent checksum handling (Wladimir J. van der Laan) d4bcd25 net: push only raw data into CConnman (Cory Fields) b79e416 net: add CVectorWriter and CNetMsgMaker (Cory Fields) 63c51d3 net: No need to check individually for disconnection anymore (Cory Fields) 07d8c7b net: don't send any messages before handshake or after fdisconnect (Cory Fields) 9adfc7f net: Set feelers to disconnect at the end of the version message (Cory Fields) f88c06c net: handle version push in InitializeNode (Cory Fields) 04d39c8 net: construct CNodeStates in place (Cory Fields) 40a6c5d net: remove now-unused ssSend and Fuzz (Cory Fields) 681c62d drop the optimistic write counter hack (Cory Fields) 9f939f3 net: switch all callers to connman for pushing messages (Cory Fields) 8f9011d connman is in charge of pushing messages (Cory Fields) f558bb7 serialization: teach serializers variadics (Cory Fields) 01ea667 net: Use deterministic randomness for CNode's nonce, and make it const (Cory Fields) de1ad13 net: constify a few CNode vars to indicate that they're threadsafe (Cory Fields) 34050a3 Move static global randomizer seeds into CConnman (Pieter Wuille) 1ce349f net: add a flag to indicate when a node's process queue is full (Fuzzbawls) 5581b47 net: add a new message queue for the message processor (Fuzzbawls) 701b578 net: rework the way that the messagehandler sleeps (Fuzzbawls) 7e55dbf net: Add a simple function for waking the message handler (Cory Fields) 47ea844 net: record bytes written before notifying the message processor (Cory Fields) ffd4859 net: handle message accounting in ReceiveMsgBytes (Cory Fields) 8cee696 net: log bytes recv/sent per command (Fuzzbawls) 754400e net: set message deserialization version when it's time to deserialize (Fuzzbawls) d2b8e0a net: make CMessageHeader a dumb storage class (Fuzzbawls) cc24eff net: remove redundant max sendbuffer size check (Fuzzbawls) 32ab0c0 net: wait until the node is destroyed to delete its recv buffer (Cory Fields) 6e3f71b net: only disconnect if fDisconnect has been set (Cory Fields) 1b0beb6 net: make GetReceiveFloodSize public (Cory Fields) 229697a net: make vRecvMsg a list so that we can use splice() (Fuzzbawls) d2d71ba net: fix typo causing the wrong receive buffer size (Cory Fields) 50bb09d Add test-before-evict discipline to addrman (Ethan Heilman) Pull request description: This is a combination of multiple upstream PRs focused on optimizing the P2P networking flow after the introduction of CConnman encapsulation, and a few older PRs that were previously missed to support the later optimizations. The PRs are as follows: - bitcoin#9037 - net: Add test-before-evict discipline to addrman - bitcoin#5151 - make CMessageHeader a dumb storage class - bitcoin#6589 - log bytes recv/sent per command - bitcoin#8688 - Move static global randomizer seeds into CConnman - bitcoin#9050 - net: make a few values immutable, and use deterministic randomness for the localnonce - bitcoin#8708 - net: have CConnman handle message sending - bitcoin#9128 - net: Decouple CConnman and message serialization - bitcoin#8822 - net: Consistent checksum handling - bitcoin#9441 - Net: Massive speedup. Net locks overhaul - bitcoin#9609 - net: fix remaining net assertions - bitcoin#9626 - Clean up a few CConnman cs_vNodes/CNode things - bitcoin#9698 - net: fix socket close race - bitcoin#9708 - Clean up all known races/platform-specific UB at the time PR was opened - Excluded bitcoin/bitcoin@512731b and bitcoin/bitcoin@d8f2b8a, to be done in a separate PR ACKs for top commit: furszy: code ACK 30d5c66 , testnet sync from scratch went well and tested with #1829 on top as well and all good. furszy: mainnet sync went fine, ACK 30d5c66 . random-zebra: ACK 30d5c66 and merging... Tree-SHA512: 09689554f53115a45f810b47ff75d887fa9097ea05992a638dbb6055262aeecd82d6ce5aaa2284003399d839b6f2c36f897413da96cfa2cd3b858387c3f752c1
Not sure if we want to have this is master.
This adds a sent/recv byte counter per command to each
CNode
.It is a waste product of some statistics gathering during finding a strategy for throttling nodes without affecting the overall quality of the network.
I think throttling the
block
response to a node with a height < self.height - 288 (TBD) would be a good approach. Nodes in initial sync might need longer to catch up if they are connected to throttled nodes only. Initial sync is a one time process and is therefore (maybe) be not very time critical.Example: