mirror of
https://github.com/espressif/openthread.git
synced 2026-08-07 11:17:46 +00:00
[topology] track connection time of neighbors (#9007)
This commit introduces a mechanism to track the connection time of neighbors as the number of seconds since the last successful link establishment with the neighbor. This feature requires the configuration `OPENTHREAD_CONFIG_UPTIME_ENABLE` to be enabled which is now enabled by default on FTD builds. The connection time of each neighbor is available in the `otNeighborInfo` and `otChildInfo` structures as the `mConnectionTime` member variable. A new CLI command, `neighbor conntime`, has been added to display the connection time and age of all neighbors.
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 (319)
|
||||
#define OPENTHREAD_API_VERSION (320)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -90,7 +90,8 @@ typedef struct otLinkModeConfig
|
||||
typedef struct
|
||||
{
|
||||
otExtAddress mExtAddress; ///< IEEE 802.15.4 Extended Address
|
||||
uint32_t mAge; ///< Time last heard
|
||||
uint32_t mAge; ///< Seconds since last heard
|
||||
uint32_t mConnectionTime; ///< Seconds since link establishment (requires `CONFIG_UPTIME_ENABLE`)
|
||||
uint16_t mRloc16; ///< RLOC16
|
||||
uint32_t mLinkFrameCounter; ///< Link Frame Counter
|
||||
uint32_t mMleFrameCounter; ///< MLE Frame Counter
|
||||
@@ -1078,6 +1079,28 @@ otError otThreadSendProactiveBackboneNotification(otInstance *aIns
|
||||
*/
|
||||
otError otThreadDetachGracefully(otInstance *aInstance, otDetachGracefullyCallback aCallback, void *aContext);
|
||||
|
||||
#define OT_DURATION_STRING_SIZE 21 ///< Recommended size for string representation of `uint32_t` duration in seconds.
|
||||
|
||||
/**
|
||||
* This function converts an `uint32_t` duration (in seconds) to a human-readable string.
|
||||
*
|
||||
* This function requires `OPENTHREAD_CONFIG_UPTIME_ENABLE` to be enabled.
|
||||
*
|
||||
* The string follows the format "<hh>:<mm>:<ss>" for hours, minutes, seconds (if duration is shorter than one day) or
|
||||
* "<dd>d.<hh>:<mm>:<ss>" (if longer than a day).
|
||||
*
|
||||
* If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be truncated
|
||||
* but the outputted string is always null-terminated.
|
||||
*
|
||||
* This function is intended for use with `mAge` or `mConnectionTime` in `otNeighborInfo` or `otChildInfo` structures.
|
||||
*
|
||||
* @param[in] aDuration A duration interval in seconds.
|
||||
* @param[out] aBuffer A pointer to a char array to output the string.
|
||||
* @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_DURATION_STRING_SIZE`.
|
||||
*
|
||||
*/
|
||||
void otConvertDurationInSecondsToString(uint32_t aDuration, char *aBuffer, uint16_t aSize);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -58,7 +58,8 @@ typedef struct
|
||||
{
|
||||
otExtAddress mExtAddress; ///< IEEE 802.15.4 Extended Address
|
||||
uint32_t mTimeout; ///< Timeout
|
||||
uint32_t mAge; ///< Time last heard
|
||||
uint32_t mAge; ///< Seconds since last heard
|
||||
uint64_t mConnectionTime; ///< Seconds since attach (requires `OPENTHREAD_CONFIG_UPTIME_ENABLE`)
|
||||
uint16_t mRloc16; ///< RLOC16
|
||||
uint16_t mChildId; ///< Child ID
|
||||
uint8_t mNetworkDataVersion; ///< Network Data Version
|
||||
|
||||
@@ -2303,6 +2303,43 @@ Print table of neighbors.
|
||||
Done
|
||||
```
|
||||
|
||||
### neighbor conntime
|
||||
|
||||
Print connection time and age of neighbors.
|
||||
|
||||
The table provides the following info per neighbor:
|
||||
|
||||
- RLOC16
|
||||
- Extended MAC address
|
||||
- Age (seconds since last heard from neighbor)
|
||||
- Connection time (seconds since link establishment with neighbor)
|
||||
|
||||
Duration intervals are formatted as `<hh>:<mm>:<ss>` for hours, minutes, and seconds if the duration is less than one day. If the duration is longer than one day, the format is `<dd>d.<hh>:<mm>:<ss>`.
|
||||
|
||||
```bash
|
||||
> neighbor conntime
|
||||
| RLOC16 | Extended MAC | Last Heard (Age) | Connection Time |
|
||||
+--------+------------------+------------------+------------------+
|
||||
| 0x8401 | 1a28be396a14a318 | 00:00:13 | 00:07:59 |
|
||||
| 0x5c00 | 723ebf0d9eba3264 | 00:00:03 | 00:11:27 |
|
||||
| 0xe800 | ce53628a1e3f5b3c | 00:00:02 | 00:00:15 |
|
||||
Done
|
||||
```
|
||||
|
||||
### neighbor conntime list
|
||||
|
||||
Print connection time and age of neighbors.
|
||||
|
||||
This command is similar to `neighbor conntime`, but it displays the information in a list format. The age and connection time are both displayed in seconds.
|
||||
|
||||
```bash
|
||||
> neighbor conntime list
|
||||
0x8401 1a28be396a14a318 age:63 conn-time:644
|
||||
0x5c00 723ebf0d9eba3264 age:23 conn-time:852
|
||||
0xe800 ce53628a1e3f5b3c age:23 conn-time:180
|
||||
Done
|
||||
```
|
||||
|
||||
### netstat
|
||||
|
||||
List all UDP sockets.
|
||||
|
||||
@@ -5352,6 +5352,84 @@ template <> otError Interpreter::Process<Cmd("neighbor")>(Arg aArgs[])
|
||||
OutputLine("| %5lu |", ToUlong(neighborInfo.mAge));
|
||||
}
|
||||
}
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
/**
|
||||
* @cli neighbor conntime
|
||||
* @code
|
||||
* neighbor conntime
|
||||
* | RLOC16 | Extended MAC | Last Heard (Age) | Connection Time |
|
||||
* +--------+------------------+------------------+------------------+
|
||||
* | 0x8401 | 1a28be396a14a318 | 00:00:13 | 00:07:59 |
|
||||
* | 0x5c00 | 723ebf0d9eba3264 | 00:00:03 | 00:11:27 |
|
||||
* | 0xe800 | ce53628a1e3f5b3c | 00:00:02 | 00:00:15 |
|
||||
* Done
|
||||
* @endcode
|
||||
* @par
|
||||
* Print the connection time and age of neighbors. Info per neighbor:
|
||||
* - RLOC16
|
||||
* - Extended MAC address
|
||||
* - Last Heard (seconds since last heard from neighbor)
|
||||
* - Connection time (seconds since link establishment with neighbor)
|
||||
* Duration intervals are formatted as `{hh}:{mm}:{ss}` for hours, minutes, and seconds if the duration is less
|
||||
* than one day. If the duration is longer than one day, the format is `{dd}d.{hh}:{mm}:{ss}`.
|
||||
*/
|
||||
else if (aArgs[0] == "conntime")
|
||||
{
|
||||
/**
|
||||
* @cli neighbor conntime list
|
||||
* @code
|
||||
* neighbor conntime list
|
||||
* 0x8401 1a28be396a14a318 age:63 conn-time:644
|
||||
* 0x5c00 723ebf0d9eba3264 age:23 conn-time:852
|
||||
* 0xe800 ce53628a1e3f5b3c age:23 conn-time:180
|
||||
* Done
|
||||
* @endcode
|
||||
* @par
|
||||
* Print connection time and age of neighbors.
|
||||
* This command is similar to `neighbor conntime`, but it displays the information in a list format. The age
|
||||
* and connection time are both displayed in seconds.
|
||||
*/
|
||||
if (aArgs[1] == "list")
|
||||
{
|
||||
isTable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const char *const kConnTimeTableTitles[] = {
|
||||
"RLOC16",
|
||||
"Extended MAC",
|
||||
"Last Heard (Age)",
|
||||
"Connection Time",
|
||||
};
|
||||
|
||||
static const uint8_t kConnTimeTableColumnWidths[] = {8, 18, 18, 18};
|
||||
|
||||
isTable = true;
|
||||
OutputTableHeader(kConnTimeTableTitles, kConnTimeTableColumnWidths);
|
||||
}
|
||||
|
||||
while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE)
|
||||
{
|
||||
if (isTable)
|
||||
{
|
||||
char string[OT_DURATION_STRING_SIZE];
|
||||
|
||||
OutputFormat("| 0x%04x | ", neighborInfo.mRloc16);
|
||||
OutputExtAddress(neighborInfo.mExtAddress);
|
||||
otConvertDurationInSecondsToString(neighborInfo.mAge, string, sizeof(string));
|
||||
OutputFormat(" | %16s", string);
|
||||
otConvertDurationInSecondsToString(neighborInfo.mConnectionTime, string, sizeof(string));
|
||||
OutputLine(" | %16s |", string);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputFormat("0x%04x ", neighborInfo.mRloc16);
|
||||
OutputExtAddress(neighborInfo.mExtAddress);
|
||||
OutputLine(" age:%lu conn-time:%lu", ToUlong(neighborInfo.mAge), ToUlong(neighborInfo.mConnectionTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
error = OT_ERROR_INVALID_ARGS;
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "common/as_core_type.hpp"
|
||||
#include "common/debug.hpp"
|
||||
#include "common/locator_getters.hpp"
|
||||
#include "common/uptime.hpp"
|
||||
#include "thread/version.hpp"
|
||||
|
||||
using namespace ot;
|
||||
@@ -466,3 +467,12 @@ otError otThreadDetachGracefully(otInstance *aInstance, otDetachGracefullyCallba
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
|
||||
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
void otConvertDurationInSecondsToString(uint32_t aDuration, char *aBuffer, uint16_t aSize)
|
||||
{
|
||||
StringWriter writer(aBuffer, aSize);
|
||||
|
||||
Uptime::UptimeToString(Uptime::SecToMsec(aDuration), writer, /* aIncludeMsec */ false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -98,7 +98,7 @@ void Logger::LogVarArgs(const char *aModuleName, LogLevel aLogLevel, const char
|
||||
static_assert(sizeof(kModuleNamePadding) == kMaxLogModuleNameLength + 1, "Padding string is not correct");
|
||||
|
||||
#if OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME
|
||||
ot::Uptime::UptimeToString(ot::Instance::Get().Get<ot::Uptime>().GetUptime(), logString);
|
||||
ot::Uptime::UptimeToString(ot::Instance::Get().Get<ot::Uptime>().GetUptime(), logString, /* aInlcudeMsec */ true);
|
||||
logString.Append(" ");
|
||||
#endif
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ void Uptime::GetUptime(char *aBuffer, uint16_t aSize) const
|
||||
{
|
||||
StringWriter writer(aBuffer, aSize);
|
||||
|
||||
UptimeToString(GetUptime(), writer);
|
||||
UptimeToString(GetUptime(), writer, /* aIncludeMsec */ true);
|
||||
}
|
||||
|
||||
void Uptime::HandleTimer(void)
|
||||
@@ -110,7 +110,7 @@ static uint16_t DivideAndGetRemainder(uint32_t &aDividend, uint32_t aDivisor)
|
||||
return static_cast<uint16_t>(quotient);
|
||||
}
|
||||
|
||||
void Uptime::UptimeToString(uint64_t aUptime, StringWriter &aWriter)
|
||||
void Uptime::UptimeToString(uint64_t aUptime, StringWriter &aWriter, bool aIncludeMsec)
|
||||
{
|
||||
uint64_t days = aUptime / Time::kOneDayInMsec;
|
||||
uint32_t remainder;
|
||||
@@ -129,7 +129,12 @@ void Uptime::UptimeToString(uint64_t aUptime, StringWriter &aWriter)
|
||||
minutes = DivideAndGetRemainder(remainder, Time::kOneMinuteInMsec);
|
||||
seconds = DivideAndGetRemainder(remainder, Time::kOneSecondInMsec);
|
||||
|
||||
aWriter.Append("%02u:%02u:%02u.%03u", hours, minutes, seconds, static_cast<uint16_t>(remainder));
|
||||
aWriter.Append("%02u:%02u:%02u", hours, minutes, seconds);
|
||||
|
||||
if (aIncludeMsec)
|
||||
{
|
||||
aWriter.Append(".%03u", static_cast<uint16_t>(remainder));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ot
|
||||
|
||||
@@ -90,13 +90,38 @@ public:
|
||||
* This method converts an uptime value (number of milliseconds) to a human-readable string.
|
||||
*
|
||||
* The string follows the format "<hh>:<mm>:<ss>.<mmmm>" for hours, minutes, seconds and millisecond (if uptime is
|
||||
* shorter than one day) or "<dd>d.<hh>:<mm>:<ss>.<mmmm>" (if longer than a day).
|
||||
* shorter than one day) or "<dd>d.<hh>:<mm>:<ss>.<mmmm>" (if longer than a day). @p aIncludeMsec can be used
|
||||
* to determine whether `.<mmm>` milliseconds is included or omitted in the resulting string.
|
||||
*
|
||||
* @param[in] aUptime The uptime to convert.
|
||||
* @param[in,out] aWriter A `StringWriter` to append the converted string to.
|
||||
* @param[in] aUptime The uptime to convert.
|
||||
* @param[in,out] aWriter A `StringWriter` to append the converted string to.
|
||||
* @param[in] aIncludeMsec Whether to include `.<mmm>` milliseconds in the string.
|
||||
*
|
||||
*/
|
||||
static void UptimeToString(uint64_t aUptime, StringWriter &aWriter);
|
||||
static void UptimeToString(uint64_t aUptime, StringWriter &aWriter, bool aIncludeMsec);
|
||||
|
||||
/**
|
||||
* This static method converts a given uptime as number of milliseconds to number of seconds.
|
||||
*
|
||||
* @param[in] aUptimeInMilliseconds Uptime in milliseconds (as `uint64_t`).
|
||||
*
|
||||
* @returns The converted @p aUptimeInMilliseconds to seconds (as `uint32_t`).
|
||||
*
|
||||
*/
|
||||
static uint32_t MsecToSec(uint64_t aUptimeInMilliseconds)
|
||||
{
|
||||
return static_cast<uint32_t>(aUptimeInMilliseconds / 1000u);
|
||||
}
|
||||
|
||||
/**
|
||||
* This static method converts a given uptime as number of seconds to number of milliseconds.
|
||||
*
|
||||
* @param[in] aUptimeInSeconds Uptime in seconds (as `uint32_t`).
|
||||
*
|
||||
* @returns The converted @p aUptimeInSeconds to milliseconds (as `uint64_t`).
|
||||
*
|
||||
*/
|
||||
static uint64_t SecToMsec(uint32_t aUptimeInSeconds) { return static_cast<uint64_t>(aUptimeInSeconds) * 1000u; }
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kTimerInterval = (1 << 30);
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
#define OPENTHREAD_CONFIG_UPTIME_ENABLE 0
|
||||
#define OPENTHREAD_CONFIG_UPTIME_ENABLE OPENTHREAD_FTD
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,29 @@
|
||||
|
||||
namespace ot {
|
||||
|
||||
void Neighbor::SetState(State aState)
|
||||
{
|
||||
VerifyOrExit(mState != aState);
|
||||
mState = static_cast<uint8_t>(aState);
|
||||
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
if (mState == kStateValid)
|
||||
{
|
||||
mConnectionStart = Uptime::MsecToSec(Get<Uptime>().GetUptime());
|
||||
}
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
uint32_t Neighbor::GetConnectionTime(void) const
|
||||
{
|
||||
return IsStateValid() ? Uptime::MsecToSec(Get<Uptime>().GetUptime()) - mConnectionStart : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Neighbor::AddressMatcher::Matches(const Neighbor &aNeighbor) const
|
||||
{
|
||||
bool matches = false;
|
||||
@@ -83,6 +106,9 @@ void Neighbor::Info::SetFrom(const Neighbor &aNeighbor)
|
||||
mFullThreadDevice = aNeighbor.IsFullThreadDevice();
|
||||
mFullNetworkData = (aNeighbor.GetNetworkDataType() == NetworkData::kFullSet);
|
||||
mVersion = aNeighbor.GetVersion();
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
mConnectionTime = aNeighbor.GetConnectionTime();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Neighbor::Init(Instance &aInstance)
|
||||
@@ -266,6 +292,9 @@ void Child::Info::SetFrom(const Child &aChild)
|
||||
#else
|
||||
mIsCslSynced = false;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
mConnectionTime = aChild.GetConnectionTime();
|
||||
#endif
|
||||
}
|
||||
|
||||
const Ip6::Address *Child::AddressIterator::GetAddress(void) const
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "common/random.hpp"
|
||||
#include "common/serial_number.hpp"
|
||||
#include "common/timer.hpp"
|
||||
#include "common/uptime.hpp"
|
||||
#include "mac/mac_types.hpp"
|
||||
#include "net/ip6.hpp"
|
||||
#include "radio/radio.hpp"
|
||||
@@ -224,7 +225,7 @@ public:
|
||||
* @param[in] aState The state value.
|
||||
*
|
||||
*/
|
||||
void SetState(State aState) { mState = static_cast<uint8_t>(aState); }
|
||||
void SetState(State aState);
|
||||
|
||||
/**
|
||||
* This method indicates whether the neighbor is in the Invalid state.
|
||||
@@ -669,6 +670,16 @@ public:
|
||||
*/
|
||||
uint8_t GetChallengeSize(void) const { return sizeof(mValidPending.mPending.mChallenge); }
|
||||
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
/**
|
||||
* This method returns the connection time (in seconds) of the neighbor (seconds since entering `kStateValid`).
|
||||
*
|
||||
* @returns The connection time (in seconds), zero if device is not currently in `kStateValid`.
|
||||
*
|
||||
*/
|
||||
uint32_t GetConnectionTime(void) const;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
/**
|
||||
* This method indicates whether or not time sync feature is enabled.
|
||||
@@ -840,6 +851,9 @@ private:
|
||||
// and this neighbor is the Subject.
|
||||
LinkMetrics::Metrics mEnhAckProbingMetrics;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_UPTIME_ENABLE
|
||||
uint32_t mConnectionStart;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
|
||||
@@ -1052,10 +1052,17 @@ add_executable(ot-test-timer
|
||||
add_executable(ot-test-toolchain
|
||||
test_toolchain.cpp test_toolchain_c.c
|
||||
)
|
||||
|
||||
target_include_directories(ot-test-toolchain
|
||||
PRIVATE
|
||||
${COMMON_INCLUDES}
|
||||
)
|
||||
|
||||
target_link_libraries(ot-test-toolchain
|
||||
PRIVATE
|
||||
${COMMON_LIBS}
|
||||
)
|
||||
|
||||
add_test(NAME ot-test-toolchain COMMAND ot-test-toolchain)
|
||||
|
||||
target_include_directories(ot-test-timer
|
||||
|
||||
Reference in New Issue
Block a user