[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:
Abtin Keshavarzian
2018-01-22 16:56:18 +00:00
committed by Jonathan Hui
parent 5e6e262dfb
commit c2d22744c0
8 changed files with 254 additions and 7 deletions
+12
View File
@@ -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);
/**
* @}
*
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
/**
+13
View File
@@ -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
*
+12
View File
@@ -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;
+64 -2
View File
@@ -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.
+111 -2
View File
@@ -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