diff --git a/examples/platforms/simulation/openthread-core-simulation-config.h b/examples/platforms/simulation/openthread-core-simulation-config.h index f71383008..252a41a1e 100644 --- a/examples/platforms/simulation/openthread-core-simulation-config.h +++ b/examples/platforms/simulation/openthread-core-simulation-config.h @@ -285,4 +285,15 @@ #define OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + * + * Set to 1 to enable support for Radio Statistics. Note that this option only works for OPENTHREAD_FTD and + * OPENTHREAD_MTD. + * + */ +#ifndef OPENTHREAD_CONFIG_RADIO_STATS_ENABLE +#define OPENTHREAD_CONFIG_RADIO_STATS_ENABLE 1 +#endif + #endif // OPENTHREAD_CORE_SIMULATION_CONFIG_H_ diff --git a/include/Makefile.am b/include/Makefile.am index f87e5b159..6f18341b5 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -78,6 +78,7 @@ openthread_headers = \ openthread/netdiag.h \ openthread/network_time.h \ openthread/ping_sender.h \ + openthread/radio_stats.h \ openthread/random_crypto.h \ openthread/random_noncrypto.h \ openthread/server.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 1c376ad87..5bc1c0967 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -105,6 +105,7 @@ source_set("openthread") { "platform/toolchain.h", "platform/trel.h", "platform/udp.h", + "radio_stats.h", "random_crypto.h", "random_noncrypto.h", "server.h", diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 1d05dd840..555b497bf 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 (331) +#define OPENTHREAD_API_VERSION (332) /** * @addtogroup api-instance diff --git a/include/openthread/radio_stats.h b/include/openthread/radio_stats.h new file mode 100644 index 000000000..259978de3 --- /dev/null +++ b/include/openthread/radio_stats.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the OpenThread API for radio stats module. + */ + +#ifndef OPENTHREAD_RADIO_STATS_H_ +#define OPENTHREAD_RADIO_STATS_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-radio-stats + * + * @{ + * + */ + +/** + * Contains the statistics of radio. + * + */ +typedef struct otRadioTimeStats +{ + uint64_t mDisabledTime; ///< The total time that radio is in disabled state, in unit of microseconds. + uint64_t mSleepTime; ///< The total time that radio is in sleep state, in unit of microseconds. + uint64_t mTxTime; ///> The total time that radio is doing transmission, in unit of microseconds. + uint64_t mRxTime; ///> The total time that radio is in receive state, in unit of microseconds. +} otRadioTimeStats; + +/** + * Gets the radio statistics. + * + * The radio statistics include the time when the radio is in TX/RX/Sleep state. These times are in units of + * microseconds. All times are calculated from the last reset of radio statistics. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns A const pointer to the otRadioTimeStats struct that contains the data. + * + */ +const otRadioTimeStats *otRadioTimeStatsGet(otInstance *aInstance); + +/** + * Resets the radio statistics. + * + * All times are reset to 0. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * + */ +void otRadioTimeStatsReset(otInstance *aInstance); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_RADIO_STATS_H_ diff --git a/script/test b/script/test index 7fc0c3ed2..8a2372598 100755 --- a/script/test +++ b/script/test @@ -446,7 +446,7 @@ do_expect() fi fi else - test_patterns=(-name 'cli-*.exp' -o -name 'simulation-*.exp') + test_patterns=(-name 'cli-*.exp' -o -name 'simulation-*.exp' -o -name 'cli_non_rcp-*.exp') fi if [[ $# != 0 ]]; then diff --git a/src/cli/README.md b/src/cli/README.md index 15a638459..5c3da43d9 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -95,6 +95,7 @@ Done - [promiscuous](#promiscuous) - [pskc](#pskc) - [pskcref](#pskcref) +- [radio](#radio-stats) - [radiofilter](#radiofilter) - [rcp](#rcp) - [region](#region) @@ -2786,6 +2787,34 @@ Disable radio promiscuous operation. Done ``` +### radio stats + +`OPENTHREAD_CONFIG_RADIO_STATS_ENABLE` is required. This feature is only available on FTD and MTD. + +The radio statistics shows the time when the radio is in sleep/tx/rx state. The command will show the time of these states since last reset in unit of microseconds. It will also show the percentage of the time. + +```bash +> radio stats +Radio Statistics: +Total Time: 67.756s +Tx Time: 0.022944s (0.03%) +Rx Time: 1.482353s (2.18%) +Sleep Time: 66.251128s (97.77%) +Disabled Time: 0.000080s (0.00%) +Done +``` + +### radio stats clear + +`OPENTHREAD_CONFIG_RADIO_STATS_ENABLE` is required. This feature is only available on FTD and MTD. + +This command resets the radio statistics. It sets all the time to 0. + +```bash +> radio stats clear +Done +``` + ### radiofilter `OPENTHREAD_CONFIG_MAC_FILTER_ENABLE` is required. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 765ff9550..0167128bf 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -86,7 +86,9 @@ #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE || OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE #include #endif - +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) +#include +#endif #include "common/new.hpp" #include "common/string.hpp" #include "mac/channel_mask.hpp" @@ -5842,6 +5844,87 @@ template <> otError Interpreter::Process(Arg aArgs[]) } #endif +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE +inline unsigned long UsToSInt(uint64_t aUs) { return ToUlong(static_cast(aUs / 1000000)); } +inline unsigned long UsToSDec(uint64_t aUs) { return ToUlong(static_cast(aUs % 1000000)); } + +void Interpreter::OutputRadioStatsTime(const char *aTimeName, uint64_t aTimeUs, uint64_t aTotalTimeUs) +{ + uint32_t timePercentInt = static_cast(aTimeUs * 100 / aTotalTimeUs); + uint32_t timePercentDec = static_cast((aTimeUs * 100 % aTotalTimeUs) * 100 / aTotalTimeUs); + + OutputLine("%s Time: %lu.%06lus (%lu.%02lu%%)", aTimeName, UsToSInt(aTimeUs), UsToSDec(aTimeUs), + ToUlong(timePercentInt), ToUlong(timePercentDec)); +} + +template <> otError Interpreter::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli radio stats + * @code + * radio stats + * Radio Statistics: + * Total Time: 67.756s + * Tx Time: 0.022944s (0.03%) + * Rx Time: 1.482353s (2.18%) + * Sleep Time: 66.251128s (97.77%) + * Disabled Time: 0.000080s (0.00%) + * Done + * @endcode + * @par api_copy + * #otRadioTimeStatsGet + */ + if (aArgs[0] == "stats") + { + if (aArgs[1].IsEmpty()) + { + const otRadioTimeStats *radioStats = nullptr; + uint64_t totalTimeUs; + + radioStats = otRadioTimeStatsGet(GetInstancePtr()); + + totalTimeUs = + radioStats->mSleepTime + radioStats->mTxTime + radioStats->mRxTime + radioStats->mDisabledTime; + if (totalTimeUs == 0) + { + OutputLine("Total Time is 0!"); + } + else + { + OutputLine("Radio Statistics:"); + OutputLine("Total Time: %lu.%03lus", ToUlong(static_cast(totalTimeUs / 1000000)), + ToUlong((totalTimeUs % 1000000) / 1000)); + OutputRadioStatsTime("Tx", radioStats->mTxTime, totalTimeUs); + OutputRadioStatsTime("Rx", radioStats->mRxTime, totalTimeUs); + OutputRadioStatsTime("Sleep", radioStats->mSleepTime, totalTimeUs); + OutputRadioStatsTime("Disabled", radioStats->mDisabledTime, totalTimeUs); + } + } + /** + * @cli radio stats clear + * @code + * radio stats clear + * Done + * @endcode + * @par api_copy + * #otRadioTimeStatsReset + */ + else if (aArgs[1] == "clear") + { + otRadioTimeStatsReset(GetInstancePtr()); + } + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + + return error; +} +#endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; @@ -7544,6 +7627,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) CmdEntry("pskcref"), #endif #endif +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + CmdEntry("radio"), +#endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE && OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE CmdEntry("radiofilter"), #endif diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index fc89231e1..2a79c7442 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -471,6 +471,9 @@ private: #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE void OutputNat64Counters(const otNat64Counters &aCounters); #endif +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + void OutputRadioStatsTime(const char *aTimeName, uint64_t aTimeUs, uint64_t aTotalTime); +#endif #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE static void HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult); diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 977a6a43e..8c9aaa4e2 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -338,6 +338,7 @@ openthread_core_files = [ "api/netdiag_api.cpp", "api/network_time_api.cpp", "api/ping_sender_api.cpp", + "api/radio_stats_api.cpp", "api/random_crypto_api.cpp", "api/random_noncrypto_api.cpp", "api/server_api.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eb1279f8a..acd33ffe6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -70,6 +70,7 @@ set(COMMON_SOURCES api/netdiag_api.cpp api/network_time_api.cpp api/ping_sender_api.cpp + api/radio_stats_api.cpp api/random_crypto_api.cpp api/random_noncrypto_api.cpp api/server_api.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 171e45525..feeb5221a 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -160,6 +160,7 @@ SOURCES_COMMON = \ api/netdiag_api.cpp \ api/network_time_api.cpp \ api/ping_sender_api.cpp \ + api/radio_stats_api.cpp \ api/random_crypto_api.cpp \ api/random_noncrypto_api.cpp \ api/server_api.cpp \ diff --git a/src/core/api/radio_stats_api.cpp b/src/core/api/radio_stats_api.cpp new file mode 100644 index 000000000..7c4b6e777 --- /dev/null +++ b/src/core/api/radio_stats_api.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread APIs for radio statistics + */ + +#include "openthread-core-config.h" + +#include + +#include "common/as_core_type.hpp" +#include "common/locator_getters.hpp" + +using namespace ot; + +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + +const otRadioTimeStats *otRadioTimeStatsGet(otInstance *aInstance) +{ + return &AsCoreType(aInstance).Get().GetStats(); +} + +void otRadioTimeStatsReset(otInstance *aInstance) { AsCoreType(aInstance).Get().ResetTime(); } + +#endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index a6ba52462..0a262bab0 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -671,6 +671,10 @@ template <> inline Radio &Instance::Get(void) { return mRadio; } template <> inline Radio::Callbacks &Instance::Get(void) { return mRadio.mCallbacks; } +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) +template <> inline RadioStatistics &Instance::Get(void) { return mRadio.mRadioStatistics; } +#endif + #if OPENTHREAD_CONFIG_UPTIME_ENABLE template <> inline Uptime &Instance::Get(void) { return mUptime; } #endif diff --git a/src/core/config/radio_link.h b/src/core/config/radio_link.h index e82ea9650..14e9a1292 100644 --- a/src/core/config/radio_link.h +++ b/src/core/config/radio_link.h @@ -55,6 +55,17 @@ #define OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + * + * Set to 1 to enable support for Radio Statistics. Note that this option only works for OPENTHREAD_FTD and + * OPENTHREAD_MTD. + * + */ +#ifndef OPENTHREAD_CONFIG_RADIO_STATS_ENABLE +#define OPENTHREAD_CONFIG_RADIO_STATS_ENABLE 0 +#endif + //-------------------------------------------------------------- #if !OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE && !OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE diff --git a/src/core/mac/mac_frame.hpp b/src/core/mac/mac_frame.hpp index 7e5bd8b29..fb4af8825 100644 --- a/src/core/mac/mac_frame.hpp +++ b/src/core/mac/mac_frame.hpp @@ -353,6 +353,15 @@ public: static constexpr uint16_t kInfoStringSize = 128; ///< Max chars for `InfoString` (ToInfoString()). + static constexpr uint8_t kPreambleSize = 4; + static constexpr uint8_t kSfdSize = 1; + static constexpr uint8_t kPhrSize = 1; + static constexpr uint8_t kPhyHeaderSize = kPreambleSize + kSfdSize + kPhrSize; + static constexpr uint8_t kFcfSize = sizeof(uint16_t); + static constexpr uint8_t kDsnSize = sizeof(uint8_t); + static constexpr uint8_t k154FcsSize = sizeof(uint16_t); + static constexpr uint8_t kImmAckLength = kFcfSize + kDsnSize + k154FcsSize; + /** * Defines the fixed-length `String` object returned from `ToInfoString()` method. * @@ -1081,12 +1090,9 @@ public: uint16_t GetFrameControlField(void) const; protected: - static constexpr uint8_t kFcfSize = sizeof(uint16_t); - static constexpr uint8_t kDsnSize = sizeof(uint8_t); static constexpr uint8_t kSecurityControlSize = sizeof(uint8_t); static constexpr uint8_t kFrameCounterSize = sizeof(uint32_t); static constexpr uint8_t kCommandIdSize = sizeof(uint8_t); - static constexpr uint8_t k154FcsSize = sizeof(uint16_t); static constexpr uint8_t kKeyIndexSize = sizeof(uint8_t); static constexpr uint16_t kFcfFrameTypeMask = 7 << 0; @@ -1119,8 +1125,6 @@ protected: static constexpr uint8_t kKeySourceSizeMode2 = 4; static constexpr uint8_t kKeySourceSizeMode3 = 8; - static constexpr uint8_t kImmAckLength = kFcfSize + kDsnSize + k154FcsSize; - static constexpr uint8_t kInvalidIndex = 0xff; static constexpr uint8_t kInvalidSize = kInvalidIndex; static constexpr uint8_t kMaxPsduSize = kInvalidSize - 1; @@ -1152,6 +1156,8 @@ protected: static uint8_t CalculateAddrFieldSize(uint16_t aFcf); static uint8_t CalculateSecurityHeaderSize(uint8_t aSecurityControl); static uint8_t CalculateMicSize(uint8_t aSecurityControl); + +public: }; /** diff --git a/src/core/radio/radio.cpp b/src/core/radio/radio.cpp index 918d838cd..9b3eb695e 100644 --- a/src/core/radio/radio.cpp +++ b/src/core/radio/radio.cpp @@ -28,7 +28,10 @@ #include "radio.hpp" +#include "common/code_utils.hpp" #include "common/locator_getters.hpp" +#include "common/timer.hpp" +#include "mac/mac_frame.hpp" #include "utils/otns.hpp" namespace ot { @@ -92,4 +95,111 @@ Error Radio::Transmit(Mac::TxFrame &aFrame) } #endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) +inline uint64_t UintSafeMinus(uint64_t aLhs, uint64_t aRhs) { return aLhs > aRhs ? (aLhs - aRhs) : 0; } + +RadioStatistics::RadioStatistics(void) + : mStatus(kDisabled) +{ + ResetTime(); +} + +void RadioStatistics::RecordStateChange(Status aStatus) +{ + UpdateTime(); + mStatus = aStatus; +} + +void RadioStatistics::HandleReceiveAt(uint32_t aDurationUs) +{ + // The actual rx time of ReceiveAt cannot be obtained from software level. This is a workaround. + if (mStatus == kSleep) + { + mTimeStats.mRxTime += aDurationUs; + } +} + +void RadioStatistics::RecordTxDone(otError aError, uint16_t aPsduLength) +{ + if (aError == kErrorNone || aError == kErrorNoAck) + { + uint32_t txTimeUs = (aPsduLength + Mac::Frame::kPhyHeaderSize) * Radio::kSymbolsPerOctet * Radio::kSymbolTime; + uint32_t rxAckTimeUs = (Mac::Frame::kImmAckLength + Mac::Frame::kPhyHeaderSize) * Radio::kPhyUsPerByte; + + UpdateTime(); + mTimeStats.mTxTime += txTimeUs; + + if (mStatus == kReceive) + { + mTimeStats.mRxTime = UintSafeMinus(mTimeStats.mRxTime, txTimeUs); + } + else if (mStatus == kSleep) + { + mTimeStats.mSleepTime = UintSafeMinus(mTimeStats.mSleepTime, txTimeUs); + if (aError == kErrorNone) + { + mTimeStats.mRxTime += rxAckTimeUs; + mTimeStats.mSleepTime = UintSafeMinus(mTimeStats.mSleepTime, rxAckTimeUs); + } + } + } +} + +void RadioStatistics::RecordRxDone(otError aError) +{ + uint32_t ackTimeUs; + + VerifyOrExit(aError == kErrorNone); + + UpdateTime(); + // Currently we cannot know the actual length of ACK. So assume the ACK is an immediate ACK. + ackTimeUs = (Mac::Frame::kImmAckLength + Mac::Frame::kPhyHeaderSize) * Radio::kPhyUsPerByte; + mTimeStats.mTxTime += ackTimeUs; + if (mStatus == kReceive) + { + mTimeStats.mRxTime = UintSafeMinus(mTimeStats.mRxTime, ackTimeUs); + } + +exit: + return; +} + +const otRadioTimeStats &RadioStatistics::GetStats(void) +{ + UpdateTime(); + + return mTimeStats; +} + +void RadioStatistics::ResetTime(void) +{ + mTimeStats.mDisabledTime = 0; + mTimeStats.mSleepTime = 0; + mTimeStats.mRxTime = 0; + mTimeStats.mTxTime = 0; + mLastUpdateTime = TimerMicro::GetNow(); +} + +void RadioStatistics::UpdateTime(void) +{ + TimeMicro nowTime = TimerMicro::GetNow(); + uint32_t timeElapsed = nowTime - mLastUpdateTime; + + switch (mStatus) + { + case kSleep: + mTimeStats.mSleepTime += timeElapsed; + break; + case kReceive: + mTimeStats.mRxTime += timeElapsed; + break; + case kDisabled: + mTimeStats.mDisabledTime += timeElapsed; + break; + } + mLastUpdateTime = nowTime; +} + +#endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + } // namespace ot diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index 4d3227443..a8d36a1a5 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -36,11 +36,13 @@ #include "openthread-core-config.h" +#include #include #include #include "common/locator.hpp" #include "common/non_copyable.hpp" +#include "common/time.hpp" #include "mac/mac_frame.hpp" namespace ot { @@ -68,6 +70,60 @@ static constexpr uint64_t kMaxCslTimeout = OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT * */ +/** + * Implements the radio statistics logic. + * + * The radio statistics are the time when the radio in TX/RX/radio state. + * Since this class collects these statistics from pure software level and no platform API is involved, a simplied + * model is used to calculate the time of different radio states. The data may not be very accurate, but it's + * sufficient to provide a general understanding of the proportion of time a device is in different radio states. + * + * The simplified model is: + * - The RadioStats is only aware of 2 states: RX and sleep. + * - Each time `Radio::Receive` or `Radio::Sleep` is called, it will check the current state and add the time since + * last time the methods were called. For example, `Sleep` is first called and `Receive` is called after 1 second, + * then 1 second will be added to SleepTime and the current state switches to `Receive`. + * - The time of TX will be calculated from the callback of TransmitDone. If TX returns OT_ERROR_NONE or + * OT_ERROR_NO_ACK, the tx time will be added according to the number of bytes sent. And the SleepTime or RxTime + * will be reduced accordingly. + * - When `GetStats` is called, an operation will be executed to calcute the time for the last state. And the result + * will be returned. + * + */ +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + +#if !OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE +#error "OPENTHREAD_CONFIG_RADIO_STATS_ENABLE requires OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE". +#endif + +class RadioStatistics +{ +public: + enum Status : uint8_t + { + kDisabled, + kSleep, + kReceive, + }; + + explicit RadioStatistics(void); + + void RecordStateChange(Status aStatus); + void HandleReceiveAt(uint32_t aDurationUs); + void RecordTxDone(otError aError, uint16_t aPsduLength); + void RecordRxDone(otError aError); + const otRadioTimeStats &GetStats(void); + void ResetTime(void); + +private: + void UpdateTime(void); + + Status mStatus; + otRadioTimeStats mTimeStats; + TimeMicro mLastUpdateTime; +}; +#endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + /** * Represents an OpenThread radio abstraction. * @@ -77,7 +133,9 @@ class Radio : public InstanceLocator, private NonCopyable friend class Instance; public: - static constexpr uint32_t kSymbolTime = OT_RADIO_SYMBOL_TIME; + static constexpr uint32_t kSymbolTime = OT_RADIO_SYMBOL_TIME; + static constexpr uint8_t kSymbolsPerOctet = OT_RADIO_SYMBOLS_PER_OCTET; + static constexpr uint32_t kPhyUsPerByte = kSymbolsPerOctet * kSymbolTime; #if (OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT && OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT) static constexpr uint16_t kNumChannelPages = 2; static constexpr uint32_t kSupportedChannels = @@ -679,6 +737,9 @@ private: otInstance *GetInstancePtr(void) const { return reinterpret_cast(&InstanceLocator::GetInstance()); } Callbacks mCallbacks; +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + RadioStatistics mRadioStatistics; +#endif }; //--------------------------------------------------------------------------------------------------------------------- @@ -744,15 +805,39 @@ inline void Radio::SetPromiscuous(bool aEnable) { otPlatRadioSetPromiscuous(GetI inline otRadioState Radio::GetState(void) { return otPlatRadioGetState(GetInstancePtr()); } -inline Error Radio::Enable(void) { return otPlatRadioEnable(GetInstancePtr()); } +inline Error Radio::Enable(void) +{ +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + mRadioStatistics.RecordStateChange(RadioStatistics::kSleep); +#endif + return otPlatRadioEnable(GetInstancePtr()); +} -inline Error Radio::Disable(void) { return otPlatRadioDisable(GetInstancePtr()); } +inline Error Radio::Disable(void) +{ +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + mRadioStatistics.RecordStateChange(RadioStatistics::kDisabled); +#endif + return otPlatRadioDisable(GetInstancePtr()); +} inline bool Radio::IsEnabled(void) { return otPlatRadioIsEnabled(GetInstancePtr()); } -inline Error Radio::Sleep(void) { return otPlatRadioSleep(GetInstancePtr()); } +inline Error Radio::Sleep(void) +{ +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + mRadioStatistics.RecordStateChange(RadioStatistics::kSleep); +#endif + return otPlatRadioSleep(GetInstancePtr()); +} -inline Error Radio::Receive(uint8_t aChannel) { return otPlatRadioReceive(GetInstancePtr(), aChannel); } +inline Error Radio::Receive(uint8_t aChannel) +{ +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + mRadioStatistics.RecordStateChange(RadioStatistics::kReceive); +#endif + return otPlatRadioReceive(GetInstancePtr(), aChannel); +} #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE inline void Radio::UpdateCslSampleTime(uint32_t aCslSampleTime) @@ -762,7 +847,14 @@ inline void Radio::UpdateCslSampleTime(uint32_t aCslSampleTime) inline Error Radio::ReceiveAt(uint8_t aChannel, uint32_t aStart, uint32_t aDuration) { - return otPlatRadioReceiveAt(GetInstancePtr(), aChannel, aStart, aDuration); + Error error = otPlatRadioReceiveAt(GetInstancePtr(), aChannel, aStart, aDuration); +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + if (error == kErrorNone) + { + mRadioStatistics.HandleReceiveAt(aDuration); + } +#endif + return error; } inline Error Radio::EnableCsl(uint32_t aCslPeriod, otShortAddress aShortAddr, const otExtAddress *aExtAddr) diff --git a/src/core/radio/radio_callbacks.cpp b/src/core/radio/radio_callbacks.cpp index d23dddc24..b66f82ac8 100644 --- a/src/core/radio/radio_callbacks.cpp +++ b/src/core/radio/radio_callbacks.cpp @@ -40,6 +40,9 @@ namespace ot { void Radio::Callbacks::HandleReceiveDone(Mac::RxFrame *aFrame, Error aError) { +#if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) + Get().RecordRxDone(aError); +#endif Get().HandleReceiveDone(aFrame, aError); } @@ -47,6 +50,9 @@ void Radio::Callbacks::HandleTransmitStarted(Mac::TxFrame &aFrame) { Get().RecordTxDone(aError, aFrame.GetPsduLength()); +#endif Get().HandleTransmitDone(aFrame, aAckFrame, aError); } diff --git a/tests/scripts/expect/cli_non_rcp-radiostats.exp b/tests/scripts/expect/cli_non_rcp-radiostats.exp new file mode 100755 index 000000000..2dcdabdf9 --- /dev/null +++ b/tests/scripts/expect/cli_non_rcp-radiostats.exp @@ -0,0 +1,57 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2023, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +setup_two_nodes "-" + +switch_node 2 +set addr [get_ipaddr mleid] +send "pollperiod 3000\n" +expect_line "Done" +send "radio stats clear\n" +expect_line "Done" + +switch_node 1 +for {set i 1} {$i <= 10} {incr i} { + send "ping $addr\n" + sleep 3 + expect "16 bytes from $addr: icmp_seq=" +} + +switch_node 2 +send "radio stats\n" +expect_line "Radio Statistics:" +expect -re {Total Time: \d+\.\d+s} +expect -re {Tx Time: \d+\.\d+s \(\d+\.\d+%\)} +expect -re {Rx Time: \d+\.\d+s \(\d+\.\d+%\)} +expect -re {Sleep Time: \d+\.\d+s \(\d+\.\d+%\)} +expect -re {Disabled Time: \d+\.\d+s \(\d+\.\d+%\)} +dispose_all diff --git a/tests/toranj/openthread-core-toranj-config-simulation.h b/tests/toranj/openthread-core-toranj-config-simulation.h index 04d6d0e56..026811319 100644 --- a/tests/toranj/openthread-core-toranj-config-simulation.h +++ b/tests/toranj/openthread-core-toranj-config-simulation.h @@ -99,4 +99,12 @@ */ #define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1 +/** + * @def OPENTHREAD_CONFIG_RADIO_STATS_ENABLE + * + * Disable the radio statistics. + * + */ +#define OPENTHREAD_CONFIG_RADIO_STATS_ENABLE 0 + #endif /* OPENTHREAD_CORE_TORANJ_CONFIG_SIMULATION_H_ */