Merge pull request #2937

971bb3e Added ping time measurement. New RPC "ping" command to request ping. Implemented "pong" message handler. New "pingtime" field in getpeerinfo, to provide results to user. New "pingwait" field, to show pings still in flight, to better see newly lagging peers. (Josh Lehan)
This commit is contained in:
Pieter Wuille 2013-10-13 17:37:02 +02:00
commit 9269d0e96e
No known key found for this signature in database
GPG key ID: 8F653255C87992E0
6 changed files with 131 additions and 4 deletions

View file

@ -229,6 +229,7 @@ static const CRPCCommand vRPCCommands[] =
{ "getbestblockhash", &getbestblockhash, true, false, false }, { "getbestblockhash", &getbestblockhash, true, false, false },
{ "getconnectioncount", &getconnectioncount, true, false, false }, { "getconnectioncount", &getconnectioncount, true, false, false },
{ "getpeerinfo", &getpeerinfo, true, false, false }, { "getpeerinfo", &getpeerinfo, true, false, false },
{ "ping", &ping, true, false, false },
{ "addnode", &addnode, true, true, false }, { "addnode", &addnode, true, true, false },
{ "getaddednodeinfo", &getaddednodeinfo, true, true, false }, { "getaddednodeinfo", &getaddednodeinfo, true, true, false },
{ "getdifficulty", &getdifficulty, true, false, false }, { "getdifficulty", &getdifficulty, true, false, false },

View file

@ -153,6 +153,7 @@ extern void EnsureWalletIsUnlocked();
extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp
extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value ping(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value addnode(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value addnode(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddednodeinfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getaddednodeinfo(const json_spirit::Array& params, bool fHelp);

View file

@ -3863,6 +3863,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
} }
else if (strCommand == "pong")
{
int64 pingUsecEnd = GetTimeMicros();
uint64 nonce = 0;
size_t nAvail = vRecv.in_avail();
bool bPingFinished = false;
std::string sProblem;
if (nAvail >= sizeof(nonce)) {
vRecv >> nonce;
// Only process pong message if there is an outstanding ping (old ping without nonce should never pong)
if (pfrom->nPingNonceSent != 0) {
if (nonce == pfrom->nPingNonceSent) {
// Matching pong received, this ping is no longer outstanding
bPingFinished = true;
int64 pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart;
if (pingUsecTime > 0) {
// Successful ping time measurement, replace previous
pfrom->nPingUsecTime = pingUsecTime;
} else {
// This should never happen
sProblem = "Timing mishap";
}
} else {
// Nonce mismatches are normal when pings are overlapping
sProblem = "Nonce mismatch";
if (nonce == 0) {
// This is most likely a bug in another implementation somewhere, cancel this ping
bPingFinished = true;
sProblem = "Nonce zero";
}
}
} else {
sProblem = "Unsolicited pong without ping";
}
} else {
// This is most likely a bug in another implementation somewhere, cancel this ping
bPingFinished = true;
sProblem = "Short payload";
}
if (!(sProblem.empty())) {
LogPrint("net", "pong %s %s: %s, %"PRI64x" expected, %"PRI64x" received, %zu bytes\n"
, pfrom->addr.ToString().c_str()
, pfrom->strSubVer.c_str()
, sProblem.c_str()
, pfrom->nPingNonceSent
, nonce
, nAvail);
}
if (bPingFinished) {
pfrom->nPingNonceSent = 0;
}
}
else if (strCommand == "alert") else if (strCommand == "alert")
{ {
CAlert alert; CAlert alert;
@ -4084,15 +4141,35 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (pto->nVersion == 0) if (pto->nVersion == 0)
return true; return true;
// Keep-alive ping. We send a nonce of zero because we don't use it anywhere //
// right now. // Message: ping
//
bool pingSend = false;
if (pto->fPingQueued) {
// RPC ping request by user
pingSend = true;
}
if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSendMsg.empty()) { if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSendMsg.empty()) {
// Ping automatically sent as a keepalive
pingSend = true;
}
if (pingSend) {
uint64 nonce = 0; uint64 nonce = 0;
if (pto->nVersion > BIP0031_VERSION) while (nonce == 0) {
RAND_bytes((unsigned char*)&nonce, sizeof(nonce));
}
pto->nPingNonceSent = nonce;
pto->fPingQueued = false;
if (pto->nVersion > BIP0031_VERSION) {
// Take timestamp as close as possible before transmitting ping
pto->nPingUsecStart = GetTimeMicros();
pto->PushMessage("ping", nonce); pto->PushMessage("ping", nonce);
else } else {
// Peer is too old to support ping command with nonce, pong will never arrive, disable timing
pto->nPingUsecStart = 0;
pto->PushMessage("ping"); pto->PushMessage("ping");
} }
}
// Start block sync // Start block sync
if (pto->fStartSync && !fImporting && !fReindex) { if (pto->fStartSync && !fImporting && !fReindex) {

View file

@ -620,6 +620,21 @@ void CNode::copyStats(CNodeStats &stats)
X(nSendBytes); X(nSendBytes);
X(nRecvBytes); X(nRecvBytes);
stats.fSyncNode = (this == pnodeSync); stats.fSyncNode = (this == pnodeSync);
// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
// Merely reporting pingtime might fool the caller into thinking the node was still responsive,
// since pingtime does not update until the ping is complete, which might take a while.
// So, if a ping is taking an unusually long time in flight,
// the caller can immediately detect that this is happening.
int64 nPingUsecWait = 0;
if ((0 != nPingNonceSent) && (0 != nPingUsecStart)) {
nPingUsecWait = GetTimeMicros() - nPingUsecStart;
}
// Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :)
stats.dPingTime = (((double)nPingUsecTime) / 1e6);
stats.dPingWait = (((double)nPingUsecWait) / 1e6);
} }
#undef X #undef X

View file

@ -119,6 +119,8 @@ public:
uint64 nSendBytes; uint64 nSendBytes;
uint64 nRecvBytes; uint64 nRecvBytes;
bool fSyncNode; bool fSyncNode;
double dPingTime;
double dPingWait;
}; };
@ -234,6 +236,12 @@ public:
CCriticalSection cs_inventory; CCriticalSection cs_inventory;
std::multimap<int64, CInv> mapAskFor; std::multimap<int64, CInv> mapAskFor;
// Ping time measurement
uint64 nPingNonceSent;
int64 nPingUsecStart;
int64 nPingUsecTime;
bool fPingQueued;
CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, MIN_PROTO_VERSION) CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, MIN_PROTO_VERSION)
{ {
nServices = 0; nServices = 0;
@ -268,6 +276,10 @@ public:
fRelayTxes = false; fRelayTxes = false;
setInventoryKnown.max_size(SendBufferSize() / 1000); setInventoryKnown.max_size(SendBufferSize() / 1000);
pfilter = new CBloomFilter(); pfilter = new CBloomFilter();
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
fPingQueued = false;
// Be shy and don't send version until we hear // Be shy and don't send version until we hear
if (hSocket != INVALID_SOCKET && !fInbound) if (hSocket != INVALID_SOCKET && !fInbound)

View file

@ -19,6 +19,24 @@ Value getconnectioncount(const Array& params, bool fHelp)
return (int)vNodes.size(); return (int)vNodes.size();
} }
Value ping(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"ping\n"
"Requests that a ping be sent to all other nodes, to measure ping time.\n"
"Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n"
"Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping.");
// Request that each node send a ping during next message processing pass
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pNode, vNodes) {
pNode->fPingQueued = true;
}
return Value::null;
}
static void CopyNodeStats(std::vector<CNodeStats>& vstats) static void CopyNodeStats(std::vector<CNodeStats>& vstats)
{ {
vstats.clear(); vstats.clear();
@ -54,6 +72,9 @@ Value getpeerinfo(const Array& params, bool fHelp)
obj.push_back(Pair("bytessent", (boost::int64_t)stats.nSendBytes)); obj.push_back(Pair("bytessent", (boost::int64_t)stats.nSendBytes));
obj.push_back(Pair("bytesrecv", (boost::int64_t)stats.nRecvBytes)); obj.push_back(Pair("bytesrecv", (boost::int64_t)stats.nRecvBytes));
obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected)); obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected));
obj.push_back(Pair("pingtime", stats.dPingTime));
if (stats.dPingWait > 0.0)
obj.push_back(Pair("pingwait", stats.dPingWait));
obj.push_back(Pair("version", stats.nVersion)); obj.push_back(Pair("version", stats.nVersion));
obj.push_back(Pair("subver", stats.strSubVer)); obj.push_back(Pair("subver", stats.strSubVer));
obj.push_back(Pair("inbound", stats.fInbound)); obj.push_back(Pair("inbound", stats.fInbound));