[ping-sender] show ping reply statistics (#6341)

This commit is contained in:
whd
2021-03-30 09:54:14 -07:00
committed by GitHub
parent b5b5994651
commit bbbeee17fb
10 changed files with 176 additions and 26 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ extern "C" {
* @note This number versions both OpenThread platform and user APIs.
*
*/
#define OPENTHREAD_API_VERSION (88)
#define OPENTHREAD_API_VERSION (89)
/**
* @addtogroup api-instance
+38 -10
View File
@@ -62,12 +62,26 @@ extern "C" {
typedef struct otPingSenderReply
{
otIp6Address mSenderAddress; ///< Sender IPv6 address (address from which ping reply was received).
uint32_t mRoundTripTime; ///< Round trip time in msec.
uint16_t mRoundTripTime; ///< Round trip time in msec.
uint16_t mSize; ///< Data size (number of bytes) in reply (excluding IPv6 and ICMP6 headers).
uint16_t mSequenceNumber; ///< Sequence number.
uint8_t mHopLimit; ///< Hop limit.
} otPingSenderReply;
/**
* This structure represents statistics of a ping request.
*
*/
typedef struct otPingSenderStatistics
{
uint16_t mSentCount; ///< The number of ping requests already sent.
uint16_t mReceivedCount; ///< The number of ping replies received.
uint32_t mTotalRoundTripTime; ///< The total round trip time of ping requests.
uint16_t mMinRoundTripTime; ///< The min round trip time among ping requests.
uint16_t mMaxRoundTripTime; ///< The max round trip time among ping requests.
bool mIsMulticast; ///< Whether this is a multicast ping request.
} otPingSenderStatistics;
/**
* This function pointer type specifies the callback to notify receipt of a ping reply.
*
@@ -75,7 +89,17 @@ typedef struct otPingSenderReply
* @param[in] aContext A pointer to application-specific context.
*
*/
typedef void (*otPingSenderCallback)(const otPingSenderReply *aReply, void *aContext);
typedef void (*otPingSenderReplyCallback)(const otPingSenderReply *aReply, void *aContext);
/**
* This function pointer type specifies the callback to report the ping statistics.
*
* @param[in] aStatistics A pointer to a `otPingSenderStatistics` containing info about the received ping
* statistics.
* @param[in] aContext A pointer to application-specific context.
*
*/
typedef void (*otPingSenderStatisticsCallback)(const otPingSenderStatistics *aStatistics, void *aContext);
/**
* This structure represents a ping request configuration.
@@ -83,14 +107,18 @@ typedef void (*otPingSenderCallback)(const otPingSenderReply *aReply, void *aCon
*/
typedef struct otPingSenderConfig
{
otIp6Address mDestination; ///< Destination address to ping.
otPingSenderCallback mCallback; ///< Callback function to report replies (can be NULL if not needed).
void * mCallbackContext; ///< A pointer to the callback application-specific context.
uint16_t mSize; ///< Data size (# of bytes) excludes IPv6/ICMPv6 header. Zero for default.
uint16_t mCount; ///< Number of ping messages to send. Zero to use default.
uint32_t mInterval; ///< Ping tx interval in milliseconds. Zero to use default.
uint8_t mHopLimit; ///< Hop limit (used if `mAllowZeroHopLimit` is false). Zero for default.
bool mAllowZeroHopLimit; ///< Indicates whether hop limit is zero.
otIp6Address mDestination; ///< Destination address to ping.
otPingSenderReplyCallback mReplyCallback; ///< Callback function to report replies (can be NULL if not needed).
otPingSenderStatisticsCallback
mStatisticsCallback; ///< Callback function to report statistics (can be NULL if not needed).
void * mCallbackContext; ///< A pointer to the callback application-specific context.
uint16_t mSize; ///< Data size (# of bytes) excludes IPv6/ICMPv6 header. Zero for default.
uint16_t mCount; ///< Number of ping messages to send. Zero to use default.
uint32_t mInterval; ///< Ping tx interval in milliseconds. Zero to use default.
uint16_t mTimeout; ///< Time in milliseconds to wait for a reply after sending out the request.
///< Zero to use default.
uint8_t mHopLimit; ///< Hop limit (used if `mAllowZeroHopLimit` is false). Zero for default.
bool mAllowZeroHopLimit; ///< Indicates whether hop limit is zero.
} otPingSenderConfig;
/**
+26 -2
View File
@@ -3155,6 +3155,29 @@ void Interpreter::HandlePingReply(const otPingSenderReply *aReply)
OutputLine(": icmp_seq=%d hlim=%d time=%dms", aReply->mSequenceNumber, aReply->mHopLimit, aReply->mRoundTripTime);
}
void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics, void *aContext)
{
static_cast<Interpreter *>(aContext)->HandlePingStatistics(aStatistics);
}
void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics)
{
OutputFormat("%u packets transmitted, %u packets received.", aStatistics->mSentCount, aStatistics->mReceivedCount);
if (!aStatistics->mIsMulticast)
{
uint32_t packetLossRate =
1000 * (aStatistics->mSentCount - aStatistics->mReceivedCount) / aStatistics->mSentCount;
OutputFormat(" Packet loss = %u.%u%%.", packetLossRate / 10, packetLossRate % 10);
}
if (aStatistics->mReceivedCount != 0)
{
uint32_t avgRoundTripTime = 1000 * aStatistics->mTotalRoundTripTime / aStatistics->mReceivedCount;
OutputFormat(" Round-trip min/avg/max = %u/%u.%u/%u ms.", aStatistics->mMinRoundTripTime,
avgRoundTripTime / 1000, avgRoundTripTime % 1000, aStatistics->mMaxRoundTripTime);
}
OutputLine("");
}
otError Interpreter::ProcessPing(uint8_t aArgsLength, char *aArgs[])
{
otError error = OT_ERROR_NONE;
@@ -3195,8 +3218,9 @@ otError Interpreter::ProcessPing(uint8_t aArgsLength, char *aArgs[])
VerifyOrExit(aArgsLength <= 5, error = OT_ERROR_INVALID_ARGS);
config.mCallback = Interpreter::HandlePingReply;
config.mCallbackContext = this;
config.mReplyCallback = Interpreter::HandlePingReply;
config.mStatisticsCallback = Interpreter::HandlePingStatistics;
config.mCallbackContext = this;
error = otPingSenderPing(mInstance, &config);
+2
View File
@@ -540,6 +540,7 @@ private:
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
static void HandlePingReply(const otPingSenderReply *aReply, void *aContext);
static void HandlePingStatistics(const otPingSenderStatistics *aStatistics, void *aContext);
#endif
static void HandleActiveScanResult(otActiveScanResult *aResult, void *aContext);
static void HandleEnergyScanResult(otEnergyScanResult *aResult, void *aContext);
@@ -582,6 +583,7 @@ private:
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
void HandlePingReply(const otPingSenderReply *aReply);
void HandlePingStatistics(const otPingSenderStatistics *aStatistics);
#endif
void HandleActiveScanResult(otActiveScanResult *aResult);
void HandleEnergyScanResult(otEnergyScanResult *aResult);
+11
View File
@@ -57,6 +57,17 @@
#define OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL 1000
#endif
/**
* @def OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_DEADLINE
*
* Specifies the default ping timeout in milliseconds. The timeout is the max acceptable time gap between each ping
* request and its reply.
*
*/
#ifndef OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_TIMEOUT
#define OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_TIMEOUT 3000
#endif
/**
* @def OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE
*
+8
View File
@@ -325,6 +325,14 @@ public:
*/
bool ShouldHandleEchoRequest(const MessageInfo &aMessageInfo);
/**
* This method returns the ICMPv6 Echo sequence number.
*
* @returns The sequence number of the next ICMPv6 Echo request.
*
*/
uint16_t GetEchoSequence(void) const { return mEchoSequence; }
private:
Error HandleEchoRequest(Message &aRequestMessage, const MessageInfo &aMessageInfo);
+56 -9
View File
@@ -60,12 +60,26 @@ void PingSender::Config::SetUnspecifiedToDefault(void)
{
mInterval = kDefaultInterval;
}
if (mTimeout == 0)
{
mTimeout = kDefaultTimeout;
}
}
void PingSender::Config::InvokeCallback(const Reply &aReply) const
void PingSender::Config::InvokeReplyCallback(const Reply &aReply) const
{
VerifyOrExit(mCallback != nullptr);
mCallback(&aReply, mCallbackContext);
VerifyOrExit(mReplyCallback != nullptr);
mReplyCallback(&aReply, mCallbackContext);
exit:
return;
}
void PingSender::Config::InvokeStatisticsCallback(const Statistics &aStatistics) const
{
VerifyOrExit(mStatisticsCallback != nullptr);
mStatisticsCallback(&aStatistics, mCallbackContext);
exit:
return;
@@ -74,6 +88,7 @@ exit:
PingSender::PingSender(Instance &aInstance)
: InstanceLocator(aInstance)
, mIdentifier(0)
, mTargetEchoSequence(0)
, mTimer(aInstance, PingSender::HandleTimer)
, mIcmpHandler(PingSender::HandleIcmpReceive, this)
{
@@ -88,8 +103,12 @@ Error PingSender::Ping(const Config &aConfig)
mConfig = aConfig;
mConfig.SetUnspecifiedToDefault();
VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
mStatistics.Clear();
mStatistics.mIsMulticast = static_cast<Ip6::Address *>(&mConfig.mDestination)->IsMulticast();
mIdentifier++;
SendPing();
@@ -123,7 +142,9 @@ void PingSender::SendPing(void)
SuccessOrExit(message->SetLength(mConfig.mSize));
}
mTargetEchoSequence = Get<Ip6::Icmp>().GetEchoSequence();
SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
mStatistics.mSentCount++;
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
@@ -135,10 +156,14 @@ exit:
FreeMessage(message);
mConfig.mCount--;
if (mConfig.mCount != 0)
if (mConfig.mCount > 0)
{
mTimer.Start(mConfig.mInterval);
}
else if (!mStatistics.mIsMulticast)
{
mTimer.Start(mConfig.mTimeout);
}
}
void PingSender::HandleTimer(Timer &aTimer)
@@ -148,7 +173,14 @@ void PingSender::HandleTimer(Timer &aTimer)
void PingSender::HandleTimer(void)
{
SendPing();
if (mConfig.mCount > 0)
{
SendPing();
}
else // The last reply times out, triggering the callback to print statistics in CLI.
{
mConfig.InvokeStatisticsCallback(mStatistics);
}
}
void PingSender::HandleIcmpReceive(void * aContext,
@@ -174,17 +206,32 @@ void PingSender::HandleIcmpReceive(const Message & aMessage,
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
timestamp = HostSwap32(timestamp);
reply.mSenderAddress = aMessageInfo.GetPeerAddr();
reply.mRoundTripTime = TimerMilli::GetNow() - TimeMilli(timestamp);
reply.mSenderAddress = aMessageInfo.GetPeerAddr();
reply.mRoundTripTime =
static_cast<uint16_t>(OT_MIN(TimerMilli::GetNow() - TimeMilli(timestamp), NumericLimits<uint16_t>::Max()));
reply.mSize = aMessage.GetLength() - aMessage.GetOffset();
reply.mSequenceNumber = aIcmpHeader.GetSequence();
reply.mHopLimit = aMessageInfo.GetHopLimit();
mStatistics.mReceivedCount++;
mStatistics.mTotalRoundTripTime += reply.mRoundTripTime;
mStatistics.mMaxRoundTripTime = OT_MAX(mStatistics.mMaxRoundTripTime, reply.mRoundTripTime);
mStatistics.mMinRoundTripTime = OT_MIN(mStatistics.mMinRoundTripTime, reply.mRoundTripTime);
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
#endif
mConfig.InvokeCallback(reply);
// Received all ping replies, no need to wait longer.
if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
{
mTimer.Stop();
}
mConfig.InvokeReplyCallback(reply);
// Received all ping replies, no need to wait longer.
if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
{
mConfig.InvokeStatisticsCallback(mStatistics);
}
exit:
return;
+25 -1
View File
@@ -44,6 +44,7 @@
#include "common/locator.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "common/numeric_limits.hpp"
#include "common/time.hpp"
#include "common/timer.hpp"
#include "net/icmp6.hpp"
@@ -65,6 +66,25 @@ public:
*/
typedef otPingSenderReply Reply;
/**
* This class represents the statistics of several ping requests.
*
*/
struct Statistics : public otPingSenderStatistics
{
Statistics(void) { Clear(); }
void Clear(void)
{
mSentCount = 0;
mReceivedCount = 0;
mTotalRoundTripTime = 0;
mMinRoundTripTime = NumericLimits<uint16_t>::Max();
mMaxRoundTripTime = NumericLimits<uint16_t>::Min();
mIsMulticast = false;
}
};
/**
* This class represents a ping request configuration.
*
@@ -100,10 +120,12 @@ public:
enum : uint32_t
{
kDefaultInterval = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL,
kDefaultTimeout = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_TIMEOUT,
};
void SetUnspecifiedToDefault(void);
void InvokeCallback(const Reply &aReply) const;
void InvokeReplyCallback(const Reply &aReply) const;
void InvokeStatisticsCallback(const Statistics &aStatistics) const;
};
/**
@@ -145,7 +167,9 @@ private:
const Ip6::Icmp::Header &aIcmpHeader);
Config mConfig;
Statistics mStatistics;
uint16_t mIdentifier;
uint16_t mTargetEchoSequence;
TimerMilli mTimer;
Ip6::Icmp::Handler mIcmpHandler;
};
+8 -3
View File
@@ -1765,10 +1765,14 @@ class NodeImpl:
result = True
# ncp-sim doesn't print Done
done = (self.node_type == 'ncp-sim')
while len(responders) < num_responses or not done:
# ncp-sim doesn't print statistics
received_statistics = (self.node_type == 'ncp-sim')
is_multicast = ipaddress.IPv6Address(ipaddr).is_multicast
while len(responders) < num_responses or not done or (not is_multicast and not received_statistics):
self.simulator.go(1)
try:
i = self._expect([r'from (\S+):', r'Done'], timeout=0.1)
i = self._expect([r'from (\S+):', r'Done', r'packets transmitted'], timeout=0.1)
except (pexpect.TIMEOUT, socket.timeout):
if self.simulator.now() < end:
continue
@@ -1781,7 +1785,8 @@ class NodeImpl:
responders[self.pexpect.match.groups()[0]] = 1
elif i == 1:
done = True
elif i == 2:
received_statistics = True
return result
def reset(self):
+1
View File
@@ -403,6 +403,7 @@ class TestOTCI(unittest.TestCase):
for dst_ip in leader.get_ipaddrs():
commissioner.ping(dst_ip, size=10, count=1, interval=2, hoplimit=3)
commissioner.wait(1)
self.assertEqual('disabled', commissioner.get_commissioiner_state())
commissioner.commissioner_start()