diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 14c9999f2..6eda35084 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -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 diff --git a/include/openthread/thread.h b/include/openthread/thread.h index 7725a2851..4bde02c14 100644 --- a/include/openthread/thread.h +++ b/include/openthread/thread.h @@ -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 "::" for hours, minutes, seconds (if duration is shorter than one day) or + * "
d.::" (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); + /** * @} * diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index 0778a0a19..f2b0db5d9 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -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 diff --git a/src/cli/README.md b/src/cli/README.md index a1326d4d2..bdc405b43 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -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 `::` for hours, minutes, and seconds if the duration is less than one day. If the duration is longer than one day, the format is `
d.::`. + +```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. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 1143d7921..6d1102358 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -5352,6 +5352,84 @@ template <> otError Interpreter::Process(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; diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp index 6ded641c3..778a663c8 100644 --- a/src/core/api/thread_api.cpp +++ b/src/core/api/thread_api.cpp @@ -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 diff --git a/src/core/common/log.cpp b/src/core/common/log.cpp index af1e6da8a..04c74bbb2 100644 --- a/src/core/common/log.cpp +++ b/src/core/common/log.cpp @@ -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().GetUptime(), logString); + ot::Uptime::UptimeToString(ot::Instance::Get().Get().GetUptime(), logString, /* aInlcudeMsec */ true); logString.Append(" "); #endif diff --git a/src/core/common/uptime.cpp b/src/core/common/uptime.cpp index 669b1ffe4..4cf0a34c7 100644 --- a/src/core/common/uptime.cpp +++ b/src/core/common/uptime.cpp @@ -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(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(remainder)); + aWriter.Append("%02u:%02u:%02u", hours, minutes, seconds); + + if (aIncludeMsec) + { + aWriter.Append(".%03u", static_cast(remainder)); + } } } // namespace ot diff --git a/src/core/common/uptime.hpp b/src/core/common/uptime.hpp index f3c33e98a..670b02343 100644 --- a/src/core/common/uptime.hpp +++ b/src/core/common/uptime.hpp @@ -90,13 +90,38 @@ public: * This method converts an uptime value (number of milliseconds) to a human-readable string. * * The string follows the format "::." for hours, minutes, seconds and millisecond (if uptime is - * shorter than one day) or "
d.::." (if longer than a day). + * shorter than one day) or "
d.::." (if longer than a day). @p aIncludeMsec can be used + * to determine whether `.` 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 `.` 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(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(aUptimeInSeconds) * 1000u; } private: static constexpr uint32_t kTimerInterval = (1 << 30); diff --git a/src/core/config/misc.h b/src/core/config/misc.h index bed58df81..c857850d1 100644 --- a/src/core/config/misc.h +++ b/src/core/config/misc.h @@ -118,7 +118,7 @@ * */ #ifndef OPENTHREAD_CONFIG_UPTIME_ENABLE -#define OPENTHREAD_CONFIG_UPTIME_ENABLE 0 +#define OPENTHREAD_CONFIG_UPTIME_ENABLE OPENTHREAD_FTD #endif /** diff --git a/src/core/thread/topology.cpp b/src/core/thread/topology.cpp index 651bf855a..ebb6c94dd 100644 --- a/src/core/thread/topology.cpp +++ b/src/core/thread/topology.cpp @@ -42,6 +42,29 @@ namespace ot { +void Neighbor::SetState(State aState) +{ + VerifyOrExit(mState != aState); + mState = static_cast(aState); + +#if OPENTHREAD_CONFIG_UPTIME_ENABLE + if (mState == kStateValid) + { + mConnectionStart = Uptime::MsecToSec(Get().GetUptime()); + } +#endif + +exit: + return; +} + +#if OPENTHREAD_CONFIG_UPTIME_ENABLE +uint32_t Neighbor::GetConnectionTime(void) const +{ + return IsStateValid() ? Uptime::MsecToSec(Get().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 diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index b08f4d0bc..62824f249 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -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(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 diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a96be494f..ca963d247 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -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