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

log bytes recv/sent per command #6589

Merged
merged 1 commit into from Dec 7, 2015

Conversation

jonasschnelli
Copy link
Contributor

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:

    "bytessent_per_cmd": {
      "addr": 30003,
      "block": 28981980,
      "getheaders": 9682,
      "headers": 82,
      "inv": 30843,
      "ping": 48,
      "pong": 40,
      "tx": 97400,
      "verack": 0,
      "version": 103
    },
    "bytesrecv_per_cmd": {
      "getaddr": 0,
      "getdata": 11935,
      "getheaders": 997,
      "headers": 1458027,
      "ping": 40,
      "pong": 40,
      "reject": 230,
      "verack": 0,
      "version": 102
    }

@@ -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)
Copy link
Member

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& ?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@sipa
Copy link
Member

sipa commented Aug 25, 2015

Concept ACK, I think this is useful.

@jonasschnelli
Copy link
Contributor Author

Thanks @sipa for reviewing!
Fixed nits. Added bytessent_per_cmd and bytesrecv_per_cmd to getpeersinfo help.

@sipa
Copy link
Member

sipa commented Aug 26, 2015 via email

@jonasschnelli
Copy link
Contributor Author

@sipa: good point! Will address it soon.

@theuni
Copy link
Member

theuni commented Aug 26, 2015

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.

@jonasschnelli
Copy link
Contributor Author

@theuni: Right. I was aware of the locking and did come to the conclusion that both new maps are protected by cs_vSend and cs_vRecvMsg.

Moved the maps to the private section.

@jonasschnelli
Copy link
Contributor Author

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++)
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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).

Copy link
Member

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;

@jgarzik
Copy link
Contributor

jgarzik commented Oct 1, 2015

ut ACK - this actually recreates a change I wrote a long time ago (first version of getpeerinfo did this)

@dcousens
Copy link
Contributor

dcousens commented Oct 4, 2015

concept ACK

edit: is it worth persisting this to storage?

@jonasschnelli
Copy link
Contributor Author

IMO
for (unsigned int i = 0; i < sizeof(logAllowIncommingCmds)/sizeof(logAllowIncommingCmds[0]); i++) is fine until we switch over to C++11.

@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.

@jonasschnelli
Copy link
Contributor Author

Passed Travis now.
Would be nice if someone can test this.

@laanwj
Copy link
Member

laanwj commented Dec 4, 2015

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++)
Copy link
Member

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;

@jonasschnelli
Copy link
Contributor Author

@laanwj: I like your map.find approach.
Implemented (squash-me commit) and renamed POSIX reserved *_t type.


UniValue recvPerCmd(UniValue::VOBJ);
BOOST_FOREACH( const mapCmdSize::value_type &i, stats.mapRecvBytesPerCmd) {
recvPerCmd.push_back(Pair(i.first, i.second));
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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).

Copy link
Contributor Author

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.

Copy link
Member

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.

@laanwj
Copy link
Member

laanwj commented Dec 4, 2015

Minimally-tested ACK, I like this functionality a lot

@laanwj
Copy link
Member

laanwj commented Dec 4, 2015

Speaking of which, why is verack accounted for 0 bytes? I guess we don't include the message header in the size? I think it should.

    "bytessent_per_cmd": {
      "addr": 31,
      "getheaders": 997,
      "inv": 9043,
      "ping": 16,
      "verack": 0,
      "version": 115
    },

@jonasschnelli
Copy link
Contributor Author

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.

@GIJensen
Copy link

GIJensen commented Dec 5, 2015

ACK

obj.push_back(Pair("bytessent_per_msg", sendPerMsgCmd));

UniValue recvPerCmd(UniValue::VOBJ);
BOOST_FOREACH( const mapMsgCmdSize::value_type &i, stats.mapRecvBytesPerMsgCmd) {
Copy link

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")

@jonasschnelli
Copy link
Contributor Author

Fixed minor code style issues (three unnecessary spaces).

@laanwj
Copy link
Member

laanwj commented Dec 7, 2015

Will merge after squash and travis OK

@jonasschnelli
Copy link
Contributor Author

Squashed.

@laanwj laanwj merged commit ca188c6 into bitcoin:master Dec 7, 2015
laanwj added a commit that referenced this pull request Dec 7, 2015
ca188c6 log bytes recv/sent per command (Jonas Schnelli)
laanwj added a commit to laanwj/bitcoin that referenced this pull request Dec 7, 2015
Looks like these were forgotten in bitcoin#6589.
GIJensen pushed a commit to GIJensen/bitcoin that referenced this pull request Dec 23, 2015
@@ -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;
Copy link
Contributor

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?

@rebroad
Copy link
Contributor

rebroad commented Oct 16, 2016

Is anyone using this functionality? If so, can uses be documented somewhere?

@maflcko
Copy link
Member

maflcko commented Oct 16, 2016

@rebroad I think the goal was to find out what msg consumes the most bytes.

OlegGirko pushed a commit to OlegGirko/dash that referenced this pull request Jun 26, 2017
Looks like these were forgotten in bitcoin#6589.
UdjinM6 pushed a commit to dashpay/dash that referenced this pull request Jun 29, 2017
…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>
lateminer pushed a commit to lateminer/bitcoin that referenced this pull request Oct 18, 2018
Looks like these were forgotten in bitcoin#6589.
random-zebra added a commit to PIVX-Project/PIVX that referenced this pull request Sep 7, 2020
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
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet