mirror of
https://github.com/espressif/openthread.git
synced 2026-08-26 20:19:53 +00:00
[ping-sender] show ping reply statistics (#6341)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user