[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:
Abtin Keshavarzian
2023-05-09 13:12:19 -07:00
committed by GitHub
parent a259d23e7f
commit c09b57392e
13 changed files with 242 additions and 13 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 (319)
#define OPENTHREAD_API_VERSION (320)
/**
* @addtogroup api-instance
+24 -1
View File
@@ -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);
/**
* @}
*
+2 -1
View File
@@ -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
+37
View File
@@ -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.
+78
View File
@@ -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;
+10
View File
@@ -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
+1 -1
View File
@@ -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
+8 -3
View File
@@ -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
+29 -4
View File
@@ -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);
+1 -1
View File
@@ -118,7 +118,7 @@
*
*/
#ifndef OPENTHREAD_CONFIG_UPTIME_ENABLE
#define OPENTHREAD_CONFIG_UPTIME_ENABLE 0
#define OPENTHREAD_CONFIG_UPTIME_ENABLE OPENTHREAD_FTD
#endif
/**
+29
View File
@@ -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
+15 -1
View File
@@ -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
+7
View File
@@ -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