@ -5,6 +5,7 @@
//
//
# include "db/write_controller.h"
# include "db/write_controller.h"
# include <array>
# include <ratio>
# include <ratio>
# include "rocksdb/system_clock.h"
# include "rocksdb/system_clock.h"
@ -26,115 +27,216 @@ class WriteControllerTest : public testing::Test {
std : : shared_ptr < TimeSetClock > clock_ ;
std : : shared_ptr < TimeSetClock > clock_ ;
} ;
} ;
TEST_F ( WriteControllerTest , ChangeDelayRateTest ) {
// Make tests easier to read
WriteController controller ( 40000000u ) ; // also set max delayed rate
# define MILLION *1000000u
controller . set_delayed_write_rate ( 10000000u ) ;
# define MB MILLION
# define MBPS MILLION
# define SECS MILLION // in microseconds
TEST_F ( WriteControllerTest , BasicAPI ) {
WriteController controller ( 40 MBPS ) ; // also set max delayed rate
EXPECT_EQ ( controller . delayed_write_rate ( ) , 40 MBPS ) ;
EXPECT_FALSE ( controller . IsStopped ( ) ) ;
EXPECT_FALSE ( controller . NeedsDelay ( ) ) ;
EXPECT_EQ ( 0 , controller . GetDelay ( clock_ . get ( ) , 100 MB ) ) ;
// set, get
controller . set_delayed_write_rate ( 20 MBPS ) ;
EXPECT_EQ ( controller . delayed_write_rate ( ) , 20 MBPS ) ;
EXPECT_FALSE ( controller . IsStopped ( ) ) ;
EXPECT_FALSE ( controller . NeedsDelay ( ) ) ;
EXPECT_EQ ( 0 , controller . GetDelay ( clock_ . get ( ) , 100 MB ) ) ;
{
// set with token, get
auto delay_token_0 = controller . GetDelayToken ( 10 MBPS ) ;
EXPECT_EQ ( controller . delayed_write_rate ( ) , 10 MBPS ) ;
EXPECT_FALSE ( controller . IsStopped ( ) ) ;
EXPECT_TRUE ( controller . NeedsDelay ( ) ) ;
// test with delay
EXPECT_EQ ( 2 SECS , controller . GetDelay ( clock_ . get ( ) , 20 MB ) ) ;
clock_ - > now_micros_ + = 2 SECS ; // pay the "debt"
auto delay_token_1 = controller . GetDelayToken ( 2 MBPS ) ;
EXPECT_EQ ( 10 SECS , controller . GetDelay ( clock_ . get ( ) , 20 MB ) ) ;
clock_ - > now_micros_ + = 10 SECS ; // pay the "debt"
auto delay_token_2 = controller . GetDelayToken ( 1 MBPS ) ;
EXPECT_EQ ( 20 SECS , controller . GetDelay ( clock_ . get ( ) , 20 MB ) ) ;
clock_ - > now_micros_ + = 20 SECS ; // pay the "debt"
auto delay_token_3 = controller . GetDelayToken ( 20 MBPS ) ;
EXPECT_EQ ( 1 SECS , controller . GetDelay ( clock_ . get ( ) , 20 MB ) ) ;
clock_ - > now_micros_ + = 1 SECS ; // pay the "debt"
// 60M is more than the max rate of 40M. Max rate will be used.
EXPECT_EQ ( controller . delayed_write_rate ( ) , 20 MBPS ) ;
auto delay_token_4 =
controller . GetDelayToken ( controller . delayed_write_rate ( ) * 3 ) ;
EXPECT_EQ ( controller . delayed_write_rate ( ) , 40 MBPS ) ;
EXPECT_EQ ( static_cast < uint64_t > ( 0.5 SECS ) ,
controller . GetDelay ( clock_ . get ( ) , 20 MB ) ) ;
EXPECT_FALSE ( controller . IsStopped ( ) ) ;
EXPECT_TRUE ( controller . NeedsDelay ( ) ) ;
// Test stop tokens
{
auto stop_token_1 = controller . GetStopToken ( ) ;
EXPECT_TRUE ( controller . IsStopped ( ) ) ;
EXPECT_EQ ( 0 , controller . GetDelay ( clock_ . get ( ) , 100 MB ) ) ;
{
auto stop_token_2 = controller . GetStopToken ( ) ;
EXPECT_TRUE ( controller . IsStopped ( ) ) ;
EXPECT_EQ ( 0 , controller . GetDelay ( clock_ . get ( ) , 100 MB ) ) ;
}
EXPECT_TRUE ( controller . IsStopped ( ) ) ;
EXPECT_EQ ( 0 , controller . GetDelay ( clock_ . get ( ) , 100 MB ) ) ;
}
// Stop tokens released
EXPECT_FALSE ( controller . IsStopped ( ) ) ;
EXPECT_TRUE ( controller . NeedsDelay ( ) ) ;
EXPECT_EQ ( controller . delayed_write_rate ( ) , 40 MBPS ) ;
// pay the previous "debt"
clock_ - > now_micros_ + = static_cast < uint64_t > ( 0.5 SECS ) ;
EXPECT_EQ ( 1 SECS , controller . GetDelay ( clock_ . get ( ) , 40 MB ) ) ;
}
// Delay tokens released
EXPECT_FALSE ( controller . NeedsDelay ( ) ) ;
}
TEST_F ( WriteControllerTest , StartFilled ) {
WriteController controller ( 10 MBPS ) ;
// Attempt to write two things that combined would be allowed within
// a single refill interval
auto delay_token_0 =
auto delay_token_0 =
controller . GetDelayToken ( controller . delayed_write_rate ( ) ) ;
controller . GetDelayToken ( controller . delayed_write_rate ( ) ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 2000000 ) ,
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
// Verify no delay because write rate has not been exceeded within
auto delay_token_1 = controller . GetDelayToken ( 2000000u ) ;
// refill interval.
ASSERT_EQ ( static_cast < uint64_t > ( 10000000 ) ,
EXPECT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 2000u /*bytes*/ ) ) ;
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
EXPECT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 2000u /*bytes*/ ) ) ;
auto delay_token_2 = controller . GetDelayToken ( 1000000u ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 20000000 ) ,
// Allow refill (kMicrosPerRefill)
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
clock_ - > now_micros_ + = 1000 ;
auto delay_token_3 = controller . GetDelayToken ( 20000000u ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 1000000 ) ,
// Again
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
EXPECT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 2000u /*bytes*/ ) ) ;
// This is more than max rate. Max delayed rate will be used.
EXPECT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 2000u /*bytes*/ ) ) ;
auto delay_token_4 =
controller . GetDelayToken ( controller . delayed_write_rate ( ) * 3 ) ;
// Control: something bigger that would exceed write rate within interval
ASSERT_EQ ( static_cast < uint64_t > ( 500000 ) ,
uint64_t delay = controller . GetDelay ( clock_ . get ( ) , 10 MB ) ;
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
EXPECT_GT ( 1.0 * delay , 0.999 SECS ) ;
EXPECT_LT ( 1.0 * delay , 1.001 SECS ) ;
}
TEST_F ( WriteControllerTest , DebtAccumulation ) {
WriteController controller ( 10 MBPS ) ;
std : : array < std : : unique_ptr < WriteControllerToken > , 10 > tokens ;
// Accumulate a time delay debt with no passage of time, like many column
// families delaying writes simultaneously. (Old versions of WriteController
// would reset the debt on every GetDelayToken.)
uint64_t debt = 0 ;
for ( unsigned i = 0 ; i < tokens . size ( ) ; + + i ) {
tokens [ i ] = controller . GetDelayToken ( ( i + 1u ) MBPS ) ;
uint64_t delay = controller . GetDelay ( clock_ . get ( ) , 63 MB ) ;
ASSERT_GT ( delay , debt ) ;
uint64_t incremental = delay - debt ;
ASSERT_EQ ( incremental , ( 63 SECS ) / ( i + 1u ) ) ;
debt + = incremental ;
}
// Pay down the debt
clock_ - > now_micros_ + = debt ;
debt = 0 ;
// Now accumulate debt with some passage of time.
for ( unsigned i = 0 ; i < tokens . size ( ) ; + + i ) {
// Debt is accumulated in time, not in bytes, so this new write
// limit is not applied to prior requested delays, even it they are
// in progress.
tokens [ i ] = controller . GetDelayToken ( ( i + 1u ) MBPS ) ;
uint64_t delay = controller . GetDelay ( clock_ . get ( ) , 63 MB ) ;
ASSERT_GT ( delay , debt ) ;
uint64_t incremental = delay - debt ;
ASSERT_EQ ( incremental , ( 63 SECS ) / ( i + 1u ) ) ;
debt + = incremental ;
uint64_t credit = debt / 2 ;
clock_ - > now_micros_ + = credit ;
debt - = credit ;
}
// Pay down the debt
clock_ - > now_micros_ + = debt ;
debt = 0 ; // consistent state
( void ) debt ; // appease clang-analyze
// Verify paid down
EXPECT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 100u /*small bytes*/ ) ) ;
// Accumulate another debt, without accounting, and releasing tokens
for ( unsigned i = 0 ; i < tokens . size ( ) ; + + i ) {
// Big and small are delayed
ASSERT_LT ( 0U , controller . GetDelay ( clock_ . get ( ) , 63 MB ) ) ;
ASSERT_LT ( 0U , controller . GetDelay ( clock_ . get ( ) , 100u /*small bytes*/ ) ) ;
tokens [ i ] . reset ( ) ;
}
// All tokens released.
// Verify that releasing all tokens pays down debt, even with no time passage.
tokens [ 0 ] = controller . GetDelayToken ( 1 MBPS ) ;
ASSERT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 100u /*small bytes*/ ) ) ;
}
}
TEST_F ( WriteControllerTest , SanityTest ) {
// This may or may not be a "good" feature, but it's an old feature
WriteController controller ( 10000000u ) ;
TEST_F ( WriteControllerTest , CreditAccumulation ) {
auto stop_token_1 = controller . GetStopToken ( ) ;
WriteController controller ( 10 MBPS ) ;
auto stop_token_2 = controller . GetStopToken ( ) ;
std : : array < std : : unique_ptr < WriteControllerToken > , 10 > tokens ;
ASSERT_TRUE ( controller . IsStopped ( ) ) ;
stop_token_1 . reset ( ) ;
// Ensure started
ASSERT_TRUE ( controller . IsStopped ( ) ) ;
tokens [ 0 ] = controller . GetDelayToken ( 1 MBPS ) ;
stop_token_2 . reset ( ) ;
ASSERT_EQ ( 10 SECS , controller . GetDelay ( clock_ . get ( ) , 10 MB ) ) ;
ASSERT_FALSE ( controller . IsStopped ( ) ) ;
clock_ - > now_micros_ + = 10 SECS ;
auto delay_token_1 = controller . GetDelayToken ( 10000000u ) ;
// Accumulate a credit
ASSERT_EQ ( static_cast < uint64_t > ( 2000000 ) ,
uint64_t credit = 1000 SECS /* see below: * 1 MB / 1 SEC */ ;
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
clock_ - > now_micros_ + = credit ;
clock_ - > now_micros_ + = 1999900u ; // sleep debt 1000
// Spend some credit (burst of I/O)
for ( unsigned i = 0 ; i < tokens . size ( ) ; + + i ) {
auto delay_token_2 = controller . GetDelayToken ( 10000000u ) ;
tokens [ i ] = controller . GetDelayToken ( ( i + 1u ) MBPS ) ;
// Rate reset after changing the token.
ASSERT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 63 MB ) ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 2000000 ) ,
// In WriteController, credit is accumulated in bytes, not in time.
controller . GetDelay ( clock_ . get ( ) , 20000000u ) ) ;
// After an "unnecessary" delay, all of our time credit will be
// translated to bytes on the next operation, in this case with
clock_ - > now_micros_ + = 1999900u ; // sleep debt 1000
// setting 1 MBPS. So regardless of the rate at delay time, we just
// account for the bytes.
// One refill: 10240 bytes allowed, 1000 used, 9240 left
credit - = 63 MB ;
ASSERT_EQ ( static_cast < uint64_t > ( 1124 ) ,
}
controller . GetDelay ( clock_ . get ( ) , 1000u ) ) ;
// Spend remaining credit
clock_ - > now_micros_ + = 1124u ; // sleep debt 0
tokens [ 0 ] = controller . GetDelayToken ( 1 MBPS ) ;
ASSERT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , credit ) ) ;
delay_token_2 . reset ( ) ;
// Verify
// 1000 used, 8240 left
ASSERT_EQ ( 10 SECS , controller . GetDelay ( clock_ . get ( ) , 10 MB ) ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 0 ) , controller . GetDelay ( clock_ . get ( ) , 1000u ) ) ;
clock_ - > now_micros_ + = 10 SECS ;
clock_ - > now_micros_ + = 100u ; // sleep credit 100
// Accumulate a credit, no accounting
// 1000 used, 7240 left
clock_ - > now_micros_ + = 1000 SECS ;
ASSERT_EQ ( static_cast < uint64_t > ( 0 ) , controller . GetDelay ( clock_ . get ( ) , 1000u ) ) ;
// Spend a small amount, releasing tokens
clock_ - > now_micros_ + = 100u ; // sleep credit 200
for ( unsigned i = 0 ; i < tokens . size ( ) ; + + i ) {
// One refill: 10240 fileed, sleep credit generates 2000. 8000 used
ASSERT_EQ ( 0U , controller . GetDelay ( clock_ . get ( ) , 3 MB ) ) ;
// 7240 + 10240 + 2000 - 8000 = 11480 left
tokens [ i ] . reset ( ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 1024u ) ,
}
controller . GetDelay ( clock_ . get ( ) , 8000u ) ) ;
// All tokens released.
clock_ - > now_micros_ + = 200u ; // sleep debt 824
// Verify credit is wiped away on new delay.
// 1000 used, 10480 left.
tokens [ 0 ] = controller . GetDelayToken ( 1 MBPS ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 0 ) , controller . GetDelay ( clock_ . get ( ) , 1000u ) ) ;
ASSERT_EQ ( 10 SECS , controller . GetDelay ( clock_ . get ( ) , 10 MB ) ) ;
clock_ - > now_micros_ + = 200u ; // sleep debt 624
// Out of bound sleep, still 10480 left
ASSERT_EQ ( static_cast < uint64_t > ( 3000624u ) ,
controller . GetDelay ( clock_ . get ( ) , 30000000u ) ) ;
clock_ - > now_micros_ + = 3000724u ; // sleep credit 100
// 6000 used, 4480 left.
ASSERT_EQ ( static_cast < uint64_t > ( 0 ) , controller . GetDelay ( clock_ . get ( ) , 6000u ) ) ;
clock_ - > now_micros_ + = 200u ; // sleep credit 300
// One refill, credit 4480 balance + 3000 credit + 10240 refill
// Use 8000, 9720 left
ASSERT_EQ ( static_cast < uint64_t > ( 1024u ) ,
controller . GetDelay ( clock_ . get ( ) , 8000u ) ) ;
clock_ - > now_micros_ + = 3024u ; // sleep credit 2000
// 1720 left
ASSERT_EQ ( static_cast < uint64_t > ( 0u ) ,
controller . GetDelay ( clock_ . get ( ) , 8000u ) ) ;
// 1720 balance + 20000 credit = 20170 left
// Use 8000, 12170 left
ASSERT_EQ ( static_cast < uint64_t > ( 0u ) ,
controller . GetDelay ( clock_ . get ( ) , 8000u ) ) ;
// 4170 left
ASSERT_EQ ( static_cast < uint64_t > ( 0u ) ,
controller . GetDelay ( clock_ . get ( ) , 8000u ) ) ;
// Need a refill
ASSERT_EQ ( static_cast < uint64_t > ( 1024u ) ,
controller . GetDelay ( clock_ . get ( ) , 9000u ) ) ;
delay_token_1 . reset ( ) ;
ASSERT_EQ ( static_cast < uint64_t > ( 0 ) ,
controller . GetDelay ( clock_ . get ( ) , 30000000u ) ) ;
delay_token_1 . reset ( ) ;
ASSERT_FALSE ( controller . IsStopped ( ) ) ;
}
}
} // namespace ROCKSDB_NAMESPACE
} // namespace ROCKSDB_NAMESPACE