mirror of
https://github.com/espressif/openthread.git
synced 2026-08-23 02:39:52 +00:00
[link-quality] adding SuccessRateTracker and tracking CCA failure rate (#2493)
This commit adds a new class `SuccessRateTracker` which can be used to tracker success/failure rate of an operation. It uses an exponentially moving average IIR filter to maintain the rate using a `uint16_t` as its storage. Unit test `test_link_quality` is updated to include a new test case ``ot::TestSuccessRateTracker()` verifying the behavior of the new class. This commit uses the new class to track the CCA failure rate (over all frame transmissions) at MAC layer.
This commit is contained in:
committed by
Jonathan Hui
parent
5e6e262dfb
commit
c2d22744c0
@@ -597,6 +597,18 @@ bool otLinkIsPromiscuous(otInstance *aInstance);
|
||||
*/
|
||||
otError otLinkSetPromiscuous(otInstance *aInstance, bool aPromiscuous);
|
||||
|
||||
|
||||
/**
|
||||
* This function returns the current CCA (Clear Channel Assessment) failure rate.
|
||||
*
|
||||
* The rate is maintained over a window of (roughly) last `OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW`
|
||||
* frame transmissions.
|
||||
*
|
||||
* @returns The CCA failure rate with maximum value `0xffff` corresponding to 100% failure rate.
|
||||
*
|
||||
*/
|
||||
uint16_t otLinkGetCcaFailureRate(otInstance *aInstance);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -365,3 +365,10 @@ otError otLinkOutOfBandTransmitRequest(otInstance *aInstance, otRadioFrame *aOob
|
||||
|
||||
return instance.GetThreadNetif().GetMac().SendOutOfBandFrameRequest(aOobFrame);
|
||||
}
|
||||
|
||||
uint16_t otLinkGetCcaFailureRate(otInstance *aInstance)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.GetThreadNetif().GetMac().GetCcaFailureRate();
|
||||
}
|
||||
|
||||
+18
-2
@@ -118,7 +118,9 @@ Mac::Mac(Instance &aInstance):
|
||||
#endif // OPENTHREAD_ENABLE_MAC_FILTER
|
||||
mTxFrame(static_cast<Frame *>(otPlatRadioGetTransmitBuffer(&aInstance))),
|
||||
mOobFrame(NULL),
|
||||
mKeyIdMode2FrameCounter(0)
|
||||
mKeyIdMode2FrameCounter(0),
|
||||
mCcaSuccessRateTracker(),
|
||||
mCcaSampleCount(0)
|
||||
{
|
||||
GenerateExtAddress(&mExtAddress);
|
||||
|
||||
@@ -1066,21 +1068,35 @@ void Mac::HandleTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otEr
|
||||
{
|
||||
Frame &sendFrame = *static_cast<Frame *>(aFrame);
|
||||
bool framePending = false;
|
||||
bool ccaSuccess = true;
|
||||
Address dstAddr;
|
||||
|
||||
// Stop the ack timer.
|
||||
|
||||
mMacTimer.Stop();
|
||||
|
||||
// Record CCA success or failure status.
|
||||
|
||||
switch (aError)
|
||||
{
|
||||
case OT_ERROR_ABORT:
|
||||
mCounters.mTxErrAbort++;
|
||||
break;
|
||||
|
||||
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
|
||||
ccaSuccess = false;
|
||||
|
||||
// fall through
|
||||
|
||||
case OT_ERROR_NONE:
|
||||
case OT_ERROR_NO_ACK:
|
||||
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
|
||||
|
||||
if (mCcaSampleCount < kMaxCcaSampleCount)
|
||||
{
|
||||
mCcaSampleCount++;
|
||||
}
|
||||
|
||||
mCcaSuccessRateTracker.AddSample(ccaSuccess, mCcaSampleCount);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
+17
-1
@@ -44,6 +44,7 @@
|
||||
#include "mac/mac_frame.hpp"
|
||||
#include "mac/mac_filter.hpp"
|
||||
#include "thread/key_manager.hpp"
|
||||
#include "thread/link_quality.hpp"
|
||||
#include "thread/network_diagnostic_tlvs.hpp"
|
||||
#include "thread/topology.hpp"
|
||||
|
||||
@@ -623,10 +624,22 @@ public:
|
||||
*/
|
||||
bool RadioSupportsRetries(void);
|
||||
|
||||
/**
|
||||
* This method returns the current CCA (Clear Channel Assessment) failure rate.
|
||||
*
|
||||
* The rate is maintained over a window of (roughly) last `OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW`
|
||||
* frame transmissions.
|
||||
*
|
||||
* @returns The CCA failure rate with maximum value `0xffff` corresponding to 100% failure rate.
|
||||
*
|
||||
*/
|
||||
uint16_t GetCcaFailureRate(void) const { return mCcaSuccessRateTracker.GetFailureRate(); }
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kInvalidRssiValue = 127
|
||||
kInvalidRssiValue = 127,
|
||||
kMaxCcaSampleCount = OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW,
|
||||
};
|
||||
|
||||
enum Operation
|
||||
@@ -739,6 +752,9 @@ private:
|
||||
|
||||
otMacCounters mCounters;
|
||||
uint32_t mKeyIdMode2FrameCounter;
|
||||
|
||||
SuccessRateTracker mCcaSuccessRateTracker;
|
||||
uint16_t mCcaSampleCount;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -923,6 +923,19 @@
|
||||
#define OPENTHREAD_CONFIG_ENABLE_STEERING_DATA_SET_OOB 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW
|
||||
*
|
||||
* OpenThread's MAC implementation maintains the average failure rate of CCA (Clear Channel Assessment) operation on
|
||||
* frame transmissions. This value specifies the window (in terms of number of transmissions or samples) over which the
|
||||
* average rate is maintained. Practically, the average value can be considered as the percentage of CCA failures in
|
||||
* (approximately) last AVERAGING_WINDOW frame transmissions.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW
|
||||
#define OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW 512
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_CHANNEL_MONITOR_SAMPLE_INTERVAL
|
||||
*
|
||||
|
||||
@@ -48,6 +48,18 @@ static const char *const kDigitsString[8] =
|
||||
"0", "125", "25", "375", "5", "625", "75", "875"
|
||||
};
|
||||
|
||||
void SuccessRateTracker::AddSample(bool aSuccess, uint16_t aWeight)
|
||||
{
|
||||
uint32_t oldAverage = mSuccessRate;
|
||||
uint32_t newValue = (aSuccess) ? kMaxRateValue : 0;
|
||||
uint32_t n = aWeight;
|
||||
|
||||
// `n/2` is added to the sum to ensure rounding the value to the nearest integer when dividing by `n`
|
||||
// (e.g., 1.2 -> 1, 3.5 -> 4).
|
||||
|
||||
mSuccessRate = static_cast<uint16_t>(((oldAverage * (n - 1)) + newValue + (n / 2)) / n);
|
||||
}
|
||||
|
||||
void RssAverager::Reset(void)
|
||||
{
|
||||
mAverage = 0;
|
||||
|
||||
@@ -50,6 +50,68 @@ namespace ot {
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class implements an operation Success Rate Tracker.
|
||||
*
|
||||
* This can be used to track different link quality related metrics, e.g., CCA failure rate, frame tx success rate).
|
||||
* The success rate is maintained using an exponential moving IIR averaging filter with a `uint16_t` as the storage.
|
||||
*
|
||||
*/
|
||||
class SuccessRateTracker
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
kMaxRateValue = 0xffff, ///< Indicates value corresponding to maximum (failure/success) rate of 100%.
|
||||
};
|
||||
|
||||
/**
|
||||
* This constructor initializes a `SuccessRateTracker` instance.
|
||||
*
|
||||
* After initialization the tracker starts with success rate 100% (failure rate 0%).
|
||||
*
|
||||
*/
|
||||
SuccessRateTracker(void): mSuccessRate(kMaxRateValue) { }
|
||||
|
||||
/**
|
||||
* This method resets the tracker to its initialized state, setting success rate to 100%.
|
||||
*
|
||||
*/
|
||||
void Reset(void) { mSuccessRate = kMaxRateValue; }
|
||||
|
||||
/**
|
||||
* This method adds a sample (success or failure) to `SuccessRateTracker`.
|
||||
*
|
||||
* @param[in] aSuccess The sample status be added, `true` for success, `false` for failure.
|
||||
* @param[in] aWeight The weight coefficient used for adding the new sample into average.
|
||||
*
|
||||
*/
|
||||
void AddSample(bool aSuccess, uint16_t aWeight = kDefaultWeight);
|
||||
|
||||
/**
|
||||
* This method returns the average failure rate.
|
||||
*
|
||||
* @retval the average failure rate `[0-kMaxRateValue]` with `kMaxRateValue` corresponding to 100%.
|
||||
*
|
||||
*/
|
||||
uint16_t GetFailureRate(void) const { return kMaxRateValue - mSuccessRate; }
|
||||
|
||||
/**
|
||||
* This method returns the average success rate.
|
||||
*
|
||||
* @retval the average success rate as [0-kMaxRateValue] with `kMaxRateValue` corresponding to 100%.
|
||||
*
|
||||
*/
|
||||
uint16_t GetSuccessRate(void) const { return mSuccessRate; }
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kDefaultWeight = 64,
|
||||
};
|
||||
|
||||
uint16_t mSuccessRate;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements a Received Signal Strength (RSS) averager.
|
||||
@@ -191,7 +253,7 @@ public:
|
||||
void AddRss(int8_t aNoiseFloor, int8_t aRss);
|
||||
|
||||
/**
|
||||
* This method returns the current average signal strength value.
|
||||
* This method returns the current average received signal strength value.
|
||||
*
|
||||
* @returns The current average value or @c OT_RADIO_RSSI_INVALID if no average is available.
|
||||
*
|
||||
@@ -208,7 +270,7 @@ public:
|
||||
uint16_t GetAverageRssRaw(void) const { return mRssAverager.GetRaw(); }
|
||||
|
||||
/**
|
||||
* This method converts the link quality info to NULL-terminated info/debug human-readable string.
|
||||
* This method converts the link quality info to NULL-terminated info/debug human-readable string.
|
||||
*
|
||||
* @param[out] aBuf A pointer to the string buffer.
|
||||
* @param[in] aSize The maximum size of the string buffer.
|
||||
|
||||
@@ -287,7 +287,7 @@ void TestRssAveraging(void)
|
||||
}
|
||||
|
||||
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// Adding two alteraing values many times:
|
||||
// Adding two alternating values many times:
|
||||
|
||||
printf("- - - - - - - - - - - - - - - - - -\n");
|
||||
|
||||
@@ -394,6 +394,114 @@ void TestLinkQualityCalculations(void)
|
||||
TestLinkQualityData(rssData4);
|
||||
}
|
||||
|
||||
void TestSuccessRateTracker(void)
|
||||
{
|
||||
SuccessRateTracker rateTracker;
|
||||
uint16_t sampleCount;
|
||||
|
||||
const uint16_t kMaxSamples = 5000;
|
||||
|
||||
const uint16_t kMaxRate = SuccessRateTracker::kMaxRateValue;
|
||||
const double kMaxError = 1.0; // Max permitted error in percentage
|
||||
const uint16_t kWeightLimit[] = { 64, 128, 256, 300, 512, 810, 900 };
|
||||
|
||||
printf("\nTesting SuccessRateTracker\n");
|
||||
|
||||
VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Initial value incorrect");
|
||||
VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Initial value incorrect");
|
||||
|
||||
// Adding all success
|
||||
for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
|
||||
{
|
||||
rateTracker.AddSample(true, sampleCount);
|
||||
|
||||
VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: incorrect rate all success case");
|
||||
VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: incorrect rate in all success case");
|
||||
}
|
||||
|
||||
rateTracker.Reset();
|
||||
VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Rate incorrect after reset");
|
||||
VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Rate incorrect after reset");
|
||||
|
||||
// Adding all failures
|
||||
for (sampleCount = 1; sampleCount < kMaxRate; sampleCount++)
|
||||
{
|
||||
rateTracker.AddSample(false, sampleCount);
|
||||
|
||||
VerifyOrQuit(rateTracker.GetSuccessRate() == 0, "SuccessRateTracker: rate incorrect all failure case");
|
||||
VerifyOrQuit(rateTracker.GetFailureRate() == kMaxRate, "SuccessRateTracker: rate incorrect in all failure case");
|
||||
}
|
||||
|
||||
// Adding success/failure at different rates and checking the RateTracker rate for every sample
|
||||
|
||||
for (uint16_t testRound = 0; testRound < sizeof(kWeightLimit) / sizeof(kWeightLimit[0]) * 2; testRound++)
|
||||
{
|
||||
uint16_t weightLimit;
|
||||
bool reverseLogic;
|
||||
double maxDiff = 0;
|
||||
|
||||
// Reverse the logic (add success instead of failure) on even test rounds
|
||||
reverseLogic = ((testRound % 2) == 0);
|
||||
|
||||
// Select a different weight limit based on the current test round
|
||||
weightLimit = kWeightLimit[testRound / 2];
|
||||
|
||||
printf("TestRound %02d, weightLimit %3d, reverseLogic %d ", testRound, weightLimit, reverseLogic);
|
||||
|
||||
for (uint16_t period = 1; period < 101; period++)
|
||||
{
|
||||
uint16_t failureCount = 0;
|
||||
|
||||
rateTracker.Reset();
|
||||
|
||||
for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
|
||||
{
|
||||
double expectedRate;
|
||||
double failureRate;
|
||||
double diff;
|
||||
bool isSuccess = ((sampleCount % period) == 0);
|
||||
uint16_t weight;
|
||||
|
||||
if (reverseLogic)
|
||||
{
|
||||
isSuccess = !isSuccess;
|
||||
}
|
||||
|
||||
weight = sampleCount;
|
||||
|
||||
if (weight > weightLimit)
|
||||
{
|
||||
weight = weightLimit;
|
||||
}
|
||||
|
||||
rateTracker.AddSample(isSuccess, weight);
|
||||
|
||||
if (!isSuccess)
|
||||
{
|
||||
failureCount++;
|
||||
}
|
||||
|
||||
// Calculate the failure rate from rateTracker and expected rate.
|
||||
|
||||
failureRate = static_cast<double>(rateTracker.GetFailureRate()) * 100.0 / kMaxRate; // in percent
|
||||
expectedRate = static_cast<double>(failureCount) * 100.0 / sampleCount; // in percent
|
||||
|
||||
diff = failureRate - expectedRate;
|
||||
diff = ABS(diff);
|
||||
|
||||
VerifyOrQuit(diff <= kMaxError, "SuccessRateTracker: rate does not match expected value");
|
||||
|
||||
if (diff > maxDiff)
|
||||
{
|
||||
maxDiff = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf(" MaxDiff = %.3f%%-> PASS\n", maxDiff);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#ifdef ENABLE_TEST_MAIN
|
||||
@@ -401,7 +509,8 @@ int main(void)
|
||||
{
|
||||
ot::TestRssAveraging();
|
||||
ot::TestLinkQualityCalculations();
|
||||
printf("All tests passed\n");
|
||||
ot::TestSuccessRateTracker();
|
||||
printf("\nAll tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user