Commit 5ead8bc2 authored by Amlan Nayak's avatar Amlan Nayak Committed by Facebook Github Bot

Add methods to update Codel parameters

Summary:
Codel is statically initialized with some default target delay and interval period. It would be useful to have the ability to update these parameters at runtime to fine tune load shedding to a given use case.

Adding methods here to update the target delay and interval parameters.

Reviewed By: yfeldblum

Differential Revision: D19987838

fbshipit-source-id: 4d67277f38037840883f6d9d5fd2d5f9be760f44
parent cbe65786
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <folly/portability/GFlags.h> #include <folly/portability/GFlags.h>
#include <algorithm> #include <algorithm>
#include <stdexcept>
DEFINE_int32(codel_interval, 100, "Codel default interval time in ms"); DEFINE_int32(codel_interval, 100, "Codel default interval time in ms");
DEFINE_int32(codel_target_delay, 5, "Target codel queueing delay in ms"); DEFINE_int32(codel_target_delay, 5, "Target codel queueing delay in ms");
...@@ -32,11 +33,12 @@ Codel::Codel() ...@@ -32,11 +33,12 @@ Codel::Codel()
.setTargetDelay(milliseconds(FLAGS_codel_target_delay))) {} .setTargetDelay(milliseconds(FLAGS_codel_target_delay))) {}
Codel::Codel(const Options& options) Codel::Codel(const Options& options)
: options_(options), : codelMinDelayNs_(0),
codelMinDelayNs_(0),
codelIntervalTimeNs_( codelIntervalTimeNs_(
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()) duration_cast<nanoseconds>(steady_clock::now().time_since_epoch())
.count()), .count()),
targetDelay_(options.targetDelay()),
interval_(options.interval()),
codelResetDelay_(true), codelResetDelay_(true),
overloaded_(false) {} overloaded_(false) {}
...@@ -47,16 +49,19 @@ bool Codel::overloaded(nanoseconds delay) { ...@@ -47,16 +49,19 @@ bool Codel::overloaded(nanoseconds delay) {
// Avoid another thread updating the value at the same time we are using it // Avoid another thread updating the value at the same time we are using it
// to calculate the overloaded state // to calculate the overloaded state
auto minDelay = nanoseconds(codelMinDelayNs_); auto minDelay = nanoseconds(codelMinDelayNs_);
// Get a snapshot of the parameters to determine overload condition
auto opts = getOptions();
auto sloughTimeout = getSloughTimeout(opts.targetDelay());
if (now > steady_clock::time_point(nanoseconds(codelIntervalTimeNs_)) && if (now > steady_clock::time_point(nanoseconds(codelIntervalTimeNs_)) &&
// testing before exchanging is more cacheline-friendly // testing before exchanging is more cacheline-friendly
(!codelResetDelay_.load(std::memory_order_acquire) && (!codelResetDelay_.load(std::memory_order_acquire) &&
!codelResetDelay_.exchange(true))) { !codelResetDelay_.exchange(true))) {
codelIntervalTimeNs_ = codelIntervalTimeNs_ =
duration_cast<nanoseconds>((now + getInterval()).time_since_epoch()) duration_cast<nanoseconds>((now + opts.interval()).time_since_epoch())
.count(); .count();
if (minDelay > getTargetDelay()) { if (minDelay > opts.targetDelay()) {
overloaded_ = true; overloaded_ = true;
} else { } else {
overloaded_ = false; overloaded_ = false;
...@@ -79,7 +84,7 @@ bool Codel::overloaded(nanoseconds delay) { ...@@ -79,7 +84,7 @@ bool Codel::overloaded(nanoseconds delay) {
// queueing delay > 2*target_delay while in the overloaded regime. This // queueing delay > 2*target_delay while in the overloaded regime. This
// empirically works better for our services than the codel approach of // empirically works better for our services than the codel approach of
// increasingly often dropping packets. // increasingly often dropping packets.
if (overloaded_ && delay > getSloughTimeout()) { if (overloaded_ && delay > sloughTimeout) {
ret = true; ret = true;
} }
...@@ -89,23 +94,41 @@ bool Codel::overloaded(nanoseconds delay) { ...@@ -89,23 +94,41 @@ bool Codel::overloaded(nanoseconds delay) {
int Codel::getLoad() { int Codel::getLoad() {
// it might be better to use the average delay instead of minDelay, but we'd // it might be better to use the average delay instead of minDelay, but we'd
// have to track it. aspiring bootcamper? // have to track it. aspiring bootcamper?
return std::min<int>(100, 100 * getMinDelay() / getSloughTimeout()); auto opts = getOptions();
return std::min<int>(
100, 100 * getMinDelay() / getSloughTimeout(opts.targetDelay()));
} }
nanoseconds Codel::getMinDelay() { void Codel::setOptions(Options const& options) {
return nanoseconds(codelMinDelayNs_); // Carry out some basic sanity checks.
auto delay = options.targetDelay();
auto interval = options.interval();
if (interval <= delay || delay <= milliseconds::zero() ||
interval <= milliseconds::zero()) {
throw std::invalid_argument("Invalid arguments provided");
}
interval_.store(interval, std::memory_order_relaxed);
targetDelay_.store(delay, std::memory_order_relaxed);
} }
milliseconds Codel::getInterval() { const Codel::Options Codel::getOptions() const {
return options_.interval(); auto interval = interval_.load(std::memory_order_relaxed);
auto delay = targetDelay_.load(std::memory_order_relaxed);
// Enforcing the invariant that targetDelay <= interval. A violation could
// potentially occur if either parameter was updated by another concurrent
// thread via the setOptions() method.
delay = std::min(delay, interval);
return Codel::Options().setTargetDelay(delay).setInterval(interval);
} }
milliseconds Codel::getTargetDelay() { nanoseconds Codel::getMinDelay() {
return options_.targetDelay(); return nanoseconds(codelMinDelayNs_);
} }
milliseconds Codel::getSloughTimeout() { milliseconds Codel::getSloughTimeout(milliseconds delay) const {
return getTargetDelay() * 2; return delay * 2;
} }
} // namespace folly } // namespace folly
...@@ -102,16 +102,40 @@ class Codel { ...@@ -102,16 +102,40 @@ class Codel {
/// Return: 0 = no delay, 100 = At the queueing limit /// Return: 0 = no delay, 100 = At the queueing limit
int getLoad(); int getLoad();
/// Update the target delay and interval parameters by passing them
/// in as an Options instance. Note that target delay must be strictly
/// smaller than the interval. This is a no-op if invalid arguments are
/// provided.
///
/// NOTE : Calls to setOptions must be externally synchronized since there
/// is no internal locking for parameter updates. Codel only guarantees
/// internal synchronization between calls to getOptions() and other members
/// but not between concurrent calls to getOptions().
///
/// Throws std::runtime_error if arguments are invalid.
void setOptions(Options const& options);
/// Return a consistent snapshot of the two parameters used by Codel. Since
/// parameters may be updated with the setOptions() method provided above,
/// it is necessary to ensure that reads of the parameters return a consistent
/// pair in which the invariant of targetDelay <= interval is guaranteed; the
/// targetDelay value that is returned is the minimum of targetDelay and
/// interval.
const Options getOptions() const;
std::chrono::nanoseconds getMinDelay(); std::chrono::nanoseconds getMinDelay();
std::chrono::milliseconds getInterval();
std::chrono::milliseconds getTargetDelay(); /// Returns the timeout condition for overload given a target delay period.
std::chrono::milliseconds getSloughTimeout(); std::chrono::milliseconds getSloughTimeout(
std::chrono::milliseconds delay) const;
private: private:
Options options_;
std::atomic<uint64_t> codelMinDelayNs_; std::atomic<uint64_t> codelMinDelayNs_;
std::atomic<uint64_t> codelIntervalTimeNs_; std::atomic<uint64_t> codelIntervalTimeNs_;
std::atomic<std::chrono::milliseconds> targetDelay_;
std::atomic<std::chrono::milliseconds> interval_;
// flag to make overloaded() thread-safe, since we only want // flag to make overloaded() thread-safe, since we only want
// to reset the delay once per time period // to reset the delay once per time period
std::atomic<bool> codelResetDelay_; std::atomic<bool> codelResetDelay_;
......
...@@ -87,3 +87,95 @@ TEST(CodelTest, getLoadSanity) { ...@@ -87,3 +87,95 @@ TEST(CodelTest, getLoadSanity) {
// this test demonstrates how silly getLoad() is, but silly isn't // this test demonstrates how silly getLoad() is, but silly isn't
// necessarily useless // necessarily useless
} }
TEST(CodelTest, updateTargetDelay) {
folly::Codel c;
folly::Codel::Options opts;
c.overloaded(milliseconds(40));
EXPECT_EQ(100, c.getLoad());
EXPECT_EQ(milliseconds(5), c.getOptions().targetDelay());
// Increase the target delay and test again.
opts.setTargetDelay(std::chrono::milliseconds(40));
opts.setInterval(std::chrono::milliseconds(100));
c.setOptions(opts);
EXPECT_EQ(milliseconds(40), c.getOptions().targetDelay());
EXPECT_FALSE(c.overloaded(milliseconds(40)));
// Decrease the target delay and test again.
opts.setTargetDelay(std::chrono::milliseconds(5));
c.setOptions(opts);
EXPECT_EQ(milliseconds(5), c.getOptions().targetDelay());
sleep_for(milliseconds(110));
EXPECT_FALSE(c.overloaded(milliseconds(40)));
EXPECT_TRUE(c.overloaded(milliseconds(40)));
}
TEST(CodelTest, updateInterval) {
folly::Codel c;
folly::Codel::Options opts;
c.overloaded(milliseconds(50));
EXPECT_EQ(100, c.getLoad());
// Make sure the default interval is correct.
EXPECT_EQ(milliseconds(100), c.getOptions().interval());
sleep_for(milliseconds(110));
// Two delayed requests lead to overload.
EXPECT_FALSE(c.overloaded(milliseconds(50)));
EXPECT_TRUE(c.overloaded(milliseconds(50)));
// Increase the interval to 200 ms and test again.
opts.setInterval(std::chrono::milliseconds(200));
opts.setTargetDelay(std::chrono::milliseconds(FLAGS_codel_target_delay));
c.setOptions(opts);
EXPECT_EQ(milliseconds(200), c.getOptions().interval());
sleep_for(milliseconds(100));
EXPECT_FALSE(c.overloaded(milliseconds(20)));
EXPECT_TRUE(c.overloaded(milliseconds(20)));
}
TEST(CodelTest, invalidParamUpdates) {
folly::Codel c;
folly::Codel::Options opts;
EXPECT_EQ(milliseconds(5), c.getOptions().targetDelay());
EXPECT_EQ(milliseconds(100), c.getOptions().interval());
// Set target delay to an invalid value.
// Can't be greater than the existing interval period.
opts.setTargetDelay(std::chrono::milliseconds(110));
try {
c.setOptions(opts);
FAIL() << "Expected a std::runtime_error";
} catch (std::invalid_argument const& err) {
std::string error = err.what();
EXPECT_EQ("Invalid arguments provided", error);
}
EXPECT_EQ(milliseconds(5), c.getOptions().targetDelay());
// Set the target delay to a valid value.
opts.setTargetDelay(std::chrono::milliseconds(20));
opts.setInterval(std::chrono::milliseconds(100));
c.setOptions(opts);
EXPECT_EQ(milliseconds(20), c.getOptions().targetDelay());
// Set the interval to a value smaller than the target delay.
opts.setInterval(std::chrono::milliseconds(5));
try {
c.setOptions(opts);
FAIL() << "Expected a std::runtime_error";
} catch (std::invalid_argument const& err) {
std::string error = err.what();
EXPECT_EQ("Invalid arguments provided", error);
}
EXPECT_EQ(milliseconds(100), c.getOptions().interval());
// Set the params to a valid combination.
opts.setInterval(std::chrono::milliseconds(200));
opts.setTargetDelay(std::chrono::milliseconds(10));
c.setOptions(opts);
EXPECT_EQ(milliseconds(10), c.getOptions().targetDelay());
EXPECT_EQ(milliseconds(200), c.getOptions().interval());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment