381 lines
11 KiB
C++
381 lines
11 KiB
C++
/**
|
|
* Copyright (c) 2021 OceanBase
|
|
* OceanBase CE is licensed under Mulan PubL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PubL v2.
|
|
* You may obtain a copy of Mulan PubL v2 at:
|
|
* http://license.coscl.org.cn/MulanPubL-2.0
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PubL v2 for more details.
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <gmock/gmock.h>
|
|
|
|
#include "all_mock.h"
|
|
#include "observer/omt/ob_multi_tenant.h"
|
|
#include "observer/omt/ob_token_calcer.h"
|
|
#include "observer/omt/ob_tenant.h"
|
|
#include "observer/omt/ob_worker_processor.h"
|
|
#include "observer/omt/ob_cgroup_ctrl.h"
|
|
|
|
using namespace std;
|
|
using namespace oceanbase::common;
|
|
using namespace oceanbase::omt;
|
|
using namespace oceanbase::observer;
|
|
|
|
static constexpr auto TIMES_OF_WORKERS = 10;
|
|
|
|
// Fake ObTenant class
|
|
class MockTenant : public ObTenant {
|
|
public:
|
|
MockTenant(const int64_t id, ObWorkerPool& worker_pool) : ObTenant(id, TIMES_OF_WORKERS, worker_pool, ctrl_)
|
|
{
|
|
stopped_ = false;
|
|
EXPECT_CALL(*this, waiting_count()).WillRepeatedly(::testing::Return(0));
|
|
}
|
|
virtual ~MockTenant()
|
|
{}
|
|
|
|
MOCK_CONST_METHOD0(waiting_count, int64_t());
|
|
|
|
private:
|
|
ObCgroupCtrl ctrl_;
|
|
};
|
|
|
|
// Fake MultiTenant class, Mocked function:
|
|
//
|
|
// get_tenant_list()
|
|
// add_tenant()
|
|
class MockOMT : public ObMultiTenant {
|
|
public:
|
|
MockOMT() : ObMultiTenant(procor_)
|
|
{}
|
|
|
|
int add_tenant(uint64_t id, double min_cpu, double max_cpu)
|
|
{
|
|
auto t = new MockTenant(id, worker_pool_);
|
|
t->set_unit_min_cpu(min_cpu);
|
|
t->set_unit_max_cpu(max_cpu);
|
|
return tenants_.push_back(t);
|
|
}
|
|
void clear()
|
|
{
|
|
std::for_each(tenants_.begin(), tenants_.end(), [](ObTenant*& t) { delete t; });
|
|
tenants_.reset();
|
|
}
|
|
|
|
private:
|
|
ObFakeWorkerProcessor procor_;
|
|
};
|
|
|
|
class TestTokenCalcer : public ::testing::Test {
|
|
public:
|
|
TestTokenCalcer() : otc_(omt_)
|
|
{
|
|
all_mock_init();
|
|
}
|
|
|
|
virtual void SetUp()
|
|
{
|
|
static constexpr auto NODE_QUOTA = 10;
|
|
EXPECT_EQ(OB_SUCCESS, omt_.init(ObAddr(), NODE_QUOTA));
|
|
}
|
|
|
|
virtual void TearDown()
|
|
{
|
|
omt_.clear();
|
|
omt_.destroy();
|
|
}
|
|
|
|
void calc()
|
|
{
|
|
otc_.calculate();
|
|
}
|
|
void clear()
|
|
{
|
|
omt_.clear();
|
|
}
|
|
|
|
protected:
|
|
ObTenant* t1_;
|
|
ObTenant* t2_;
|
|
ObTenant* t3_;
|
|
MockOMT omt_;
|
|
ObTokenCalcer otc_;
|
|
};
|
|
|
|
//// Rule Min:
|
|
//
|
|
// (1) Tenant would get at least tokens of integral part of min CPU
|
|
// slice.
|
|
//
|
|
// (2) The fractional part of tenant min CPU slice should be
|
|
// accumulated but less than at most one token. When Tenant has
|
|
// waiting task, it will be used.
|
|
//
|
|
TEST_F(TestTokenCalcer, RuleMin)
|
|
{
|
|
ASSERT_EQ(10, omt_.get_node_quota());
|
|
auto& tenants{omt_.get_tenant_list()};
|
|
auto ID{1};
|
|
|
|
// Number of tokens no more than (9.9999*2)
|
|
ASSERT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 9.9999, 10));
|
|
const auto& t{tenants[0]};
|
|
for (auto i = 0; i < 100; i++) {
|
|
calc();
|
|
ASSERT_LE(19, t->sug_token_cnt());
|
|
}
|
|
clear();
|
|
|
|
for (auto i = 0; i < 10; i++) {
|
|
ASSERT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0.4999, 0.4999));
|
|
}
|
|
calc();
|
|
for (auto i = 0; i < 10; i++) {
|
|
ASSERT_EQ(0, tenants[i]->sug_token_cnt());
|
|
}
|
|
calc();
|
|
for (auto i = 0; i < 10; i++) {
|
|
// According to RuleMax1 and RuleMax2
|
|
ASSERT_EQ(1, tenants[i]->sug_token_cnt());
|
|
}
|
|
}
|
|
|
|
//// Rule Max:
|
|
//
|
|
// (1) The Number of Tenant tokens won't greater than its max tokens
|
|
// count which is ceiling of tenant CPU
|
|
// slice(quota*concurrency). e.g. Given tenant's max CPU quota is
|
|
// 1.6 and concurrency is 2, tenant's max slice is 3.2 and tenant
|
|
// won't get tokens greater than 4, ceil(3.2).
|
|
//
|
|
// (2) Tenant max CPU slice's fractional part would be accumulated and
|
|
// consumed whenever the accumulation is enough to compose one token.
|
|
//
|
|
TEST_F(TestTokenCalcer, RuleMax)
|
|
{
|
|
ASSERT_EQ(10, omt_.get_node_quota());
|
|
auto& tenants{omt_.get_tenant_list()};
|
|
auto ID{1};
|
|
|
|
{
|
|
// max CPU quota: 0.3
|
|
// max CPU slice: 0.6
|
|
ASSERT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0.1, 0.3));
|
|
const auto& t{tenants[0]};
|
|
calc();
|
|
ASSERT_EQ(0, t->sug_token_cnt()); // 0.6
|
|
calc();
|
|
ASSERT_EQ(1, t->sug_token_cnt()); // 1.2 - 1.0 = 0.2
|
|
calc();
|
|
ASSERT_EQ(0, t->sug_token_cnt()); // 0.8
|
|
calc();
|
|
ASSERT_EQ(1, t->sug_token_cnt()); // 1.4 - 1.0 = 0.4
|
|
// NOTE: Following code won't generate a token because above
|
|
// calculation lose some precision. It's right although it is
|
|
// not by design.
|
|
calc();
|
|
ASSERT_EQ(0, t->sug_token_cnt()); // 1.0(-)
|
|
calc();
|
|
ASSERT_EQ(1, t->sug_token_cnt()); // 1.6(-) - 1.0 = 0.6(-)
|
|
calc();
|
|
ASSERT_EQ(1, t->sug_token_cnt()); // 1.2(-) - 1.0 = 0.2(-)
|
|
calc();
|
|
ASSERT_EQ(0, t->sug_token_cnt()); // 0.8(-)
|
|
calc();
|
|
ASSERT_EQ(1, t->sug_token_cnt()); // 1.4(-) - 1.0 = 0.4(-)
|
|
calc();
|
|
ASSERT_EQ(0, t->sug_token_cnt()); // 1.0(-)
|
|
}
|
|
{
|
|
// max CPU quota: 1.4
|
|
// max CPU slice: 2.8
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0.1, 1.4));
|
|
const auto& t{tenants[1]};
|
|
calc();
|
|
ASSERT_EQ(2, t->sug_token_cnt()); // 2.8 - 2.0 = 0.8
|
|
calc();
|
|
ASSERT_EQ(3, t->sug_token_cnt()); // 3.6 - 3.0 = 0.6
|
|
calc();
|
|
ASSERT_EQ(3, t->sug_token_cnt()); // 3.4 - 3.0 = 0.4
|
|
calc();
|
|
ASSERT_EQ(3, t->sug_token_cnt()); // 3.2 - 3.0 = 0.2
|
|
calc();
|
|
ASSERT_EQ(2, t->sug_token_cnt()); // 3.0(-) - 2.0 = 1.0(-)
|
|
calc();
|
|
ASSERT_EQ(3, t->sug_token_cnt()); // 3.8(-) - 3.0 = 0.8(-)
|
|
}
|
|
}
|
|
|
|
// Rule Normal Sell
|
|
//
|
|
// (1) If sum of all tenants max tokens is less than or equal to node
|
|
// tokens, every tenant will get tokens according to its max
|
|
// tokens according to RuleMax.
|
|
//
|
|
TEST_F(TestTokenCalcer, NormalSell)
|
|
{
|
|
ASSERT_EQ(10, omt_.get_node_quota());
|
|
auto& tenants = omt_.get_tenant_list();
|
|
|
|
{
|
|
// Create a tenant with 1 max CPU and 0.1 min CPU, it would get 1
|
|
// CPU i.e. 2 tokens.
|
|
auto ID = 1;
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID, 0, 10));
|
|
calc();
|
|
EXPECT_EQ(20, tenants[0]->sug_token_cnt());
|
|
clear();
|
|
}
|
|
{
|
|
// Create another 10 tenants with 1 max CPU, each would get 1 CPU
|
|
// i.e. 2 tokens.
|
|
auto ID = 1;
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, 1));
|
|
}
|
|
calc();
|
|
for_each(tenants.begin(), tenants.end(), [](ObTenant*& t) { EXPECT_EQ(2, t->sug_token_cnt()); });
|
|
clear();
|
|
}
|
|
{
|
|
// Create 20 tenants with 0.5 max CPU, each would get 1 token.
|
|
auto ID = 1;
|
|
for (int i = 0; i < 20; i++) {
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, .5));
|
|
}
|
|
calc();
|
|
for_each(tenants.begin(), tenants.end(), [](ObTenant*& t) { EXPECT_EQ(1, t->sug_token_cnt()); });
|
|
clear();
|
|
}
|
|
{
|
|
// Create 10 tenants with 0.5 max CPU, and one with 5 max CPU.
|
|
auto ID = 1;
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, .5));
|
|
}
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, 5));
|
|
for (int i = 0; i < 100; i++) { // iterate N times
|
|
calc();
|
|
for_each( // All tenants but the last have 1 token.
|
|
tenants.begin(),
|
|
tenants.end() - 1,
|
|
[](ObTenant*& t) { EXPECT_EQ(1, t->sug_token_cnt()); });
|
|
for_each( // Last tenant have 10 tokens.
|
|
tenants.end() - 1,
|
|
tenants.end(),
|
|
[](ObTenant*& t) { EXPECT_EQ(10, t->sug_token_cnt()); });
|
|
}
|
|
}
|
|
}
|
|
|
|
//// Rule Over Sell
|
|
//
|
|
// (1) If sum of all tenants max tokens is greater than node tokens,
|
|
// tenants would get tokens according to corresponding number of
|
|
// waiting tasks. In this case, tenant with more waiting tasks
|
|
// would get more tokens than that with less waiting tasks but
|
|
// also need obey max_tokens limitation. Tenants will share node
|
|
// tokens equally in a particully case that all tenants have no
|
|
// waiting tasks.
|
|
//
|
|
TEST_F(TestTokenCalcer, OverSell)
|
|
{
|
|
ASSERT_EQ(10, omt_.get_node_quota());
|
|
auto& tenants = omt_.get_tenant_list();
|
|
|
|
{
|
|
// Create 10 tenants with 2 max CPU, because there is only 10 CPU,
|
|
// so each would get 1(2 tokens).
|
|
auto ID = 1;
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, 2));
|
|
}
|
|
calc();
|
|
for_each(tenants.begin(), tenants.end(), [](ObTenant*& t) { EXPECT_EQ(2, t->sug_token_cnt()); });
|
|
clear();
|
|
}
|
|
{
|
|
// Tenants specification:
|
|
// (min,max) => (0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(0,7),(0,8),(0,9)
|
|
auto ID = 1;
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_EQ(OB_SUCCESS, omt_.add_tenant(ID++, 0, i + 1));
|
|
}
|
|
|
|
// 1) If all of 10 tenants don't have tasks, they'll share node
|
|
// tokens equally.
|
|
calc();
|
|
for_each(tenants.begin(), tenants.end(), [](ObTenant*& t) {
|
|
EXPECT_EQ(2, t->sug_token_cnt()) << "max: " << t->unit_max_cpu();
|
|
});
|
|
|
|
// 2) If all of tenants have only one waiting task, It's same as
|
|
// previous condition because previous equally assigning if
|
|
// "enough" for every tenant, each with two token can handle
|
|
// the ONLY ONE task.
|
|
for (int i = 0; i < 10; i++) {
|
|
auto& t = *reinterpret_cast<MockTenant*>(tenants[i]);
|
|
// ::testing::Mock::VerifyAndClearExpectations(&t);
|
|
EXPECT_CALL(t, waiting_count()).WillRepeatedly(::testing::Return(1));
|
|
ASSERT_EQ(1, t.waiting_count());
|
|
}
|
|
calc();
|
|
for_each(tenants.begin(), tenants.end(), [](ObTenant*& t) {
|
|
EXPECT_EQ(2, t->sug_token_cnt()) << "max: " << t->unit_max_cpu();
|
|
});
|
|
|
|
// 3) If each tenant has different number of waiting tasks, how
|
|
// many tokens will each tenant would get is depend on its MAX
|
|
// QUOTA and number of waiting tasks. The more a tenant has MAX
|
|
// QUOTA or more waiting tasks, more tokens will the tenant
|
|
// got.
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_CALL(*reinterpret_cast<MockTenant*>(tenants[i]), waiting_count()).WillRepeatedly(::testing::Return(i));
|
|
ASSERT_EQ(i, tenants[i]->waiting_count());
|
|
}
|
|
calc();
|
|
for (int i = 0; i < 10; i++) {
|
|
auto& t = tenants[i];
|
|
// It depends but it's the result with this waiting tasks
|
|
// distribution.
|
|
EXPECT_EQ(i / 2, t->sug_token_cnt());
|
|
}
|
|
|
|
// 4) Same as 3) but given tenants have many waiting tasks, tokens
|
|
// distribution would obey tenant's MAX QUOTA specification.
|
|
for (int i = 0; i < 10; i++) {
|
|
auto& t = *reinterpret_cast<MockTenant*>(tenants[i]);
|
|
// ::testing::Mock::VerifyAndClearExpectations(&t);
|
|
EXPECT_CALL(t, waiting_count()).WillRepeatedly(::testing::Return(100));
|
|
ASSERT_EQ(100, t.waiting_count());
|
|
}
|
|
calc();
|
|
auto p = adjacent_find( // Search left tenant has tokens greater
|
|
// than right. It should be none.
|
|
tenants.begin(),
|
|
tenants.end(),
|
|
[](ObTenant* t1, ObTenant* t2) -> bool { return t1->sug_token_cnt() > t2->sug_token_cnt(); });
|
|
EXPECT_EQ(tenants.end(), p);
|
|
clear();
|
|
}
|
|
}
|
|
|
|
TEST_F(TestTokenCalcer, Misc)
|
|
{
|
|
// If no tenant exist.
|
|
calc();
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
::testing::InitGoogleMock(&argc, argv);
|
|
if (argc > 1) {
|
|
OB_LOGGER.set_log_level(3);
|
|
}
|
|
return RUN_ALL_TESTS();
|
|
}
|