From 4b0b93ad008f1e0e4d48e1b0ece434cb1cfa1a34 Mon Sep 17 00:00:00 2001 From: Li Cao Date: Thu, 15 Jun 2023 04:30:02 +0800 Subject: [PATCH] [radio] add radio statistics of tx/rx/sleep cycle (#9071) This commit implements time counting in radio to collect the time when radio is in sleep/tx/rx time. 2 new APIs are added to get and reset the statistics. This feature is only available on FTD and MTD. This PR also adds cli commands to call the APIs on example cli firmware. This feature is by default disabled in OT core config. It's enabled on simulation platform by default now. Regarding implementation, this feature is totally implemented by software. It uses a simplified model to calculate the time. It may not be very accurate, but it should be enough to give us an overview of the time. --- .../openthread-core-simulation-config.h | 11 ++ include/Makefile.am | 1 + include/openthread/BUILD.gn | 1 + include/openthread/instance.h | 2 +- include/openthread/radio_stats.h | 99 ++++++++++++++++ script/test | 2 +- src/cli/README.md | 29 +++++ src/cli/cli.cpp | 88 +++++++++++++- src/cli/cli.hpp | 3 + src/core/BUILD.gn | 1 + src/core/CMakeLists.txt | 1 + src/core/Makefile.am | 1 + src/core/api/radio_stats_api.cpp | 52 +++++++++ src/core/common/instance.hpp | 4 + src/core/config/radio_link.h | 11 ++ src/core/mac/mac_frame.hpp | 16 ++- src/core/radio/radio.cpp | 110 ++++++++++++++++++ src/core/radio/radio.hpp | 104 ++++++++++++++++- src/core/radio/radio_callbacks.cpp | 6 + .../scripts/expect/cli_non_rcp-radiostats.exp | 57 +++++++++ ...openthread-core-toranj-config-simulation.h | 8 ++ 21 files changed, 593 insertions(+), 14 deletions(-) create mode 100644 include/openthread/radio_stats.h create mode 100644 src/core/api/radio_stats_api.cpp create mode 100755 tests/scripts/expect/cli_non_rcp-radiostats.exp 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_ */