blob: 781a5a51de9ad70bcc7ccb25d472eaca61f0af58 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "server/hotkey_collector.h"
#include <dsn/utility/rand.h>
#include <dsn/utility/flags.h>
#include <dsn/utility/defer.h>
#include <dsn/tool-api/task_tracker.h>
#include "server/test/message_utils.h"
#include "base/pegasus_key_schema.h"
#include "pegasus_server_test_base.h"
namespace pegasus {
namespace server {
DSN_DECLARE_uint32(hotkey_buckets_num);
static std::string generate_hash_key_by_random(bool is_hotkey, int probability = 100)
{
if (is_hotkey && (dsn::rand::next_u32(100) < probability)) {
return "ThisisahotkeyThisisahotkey";
}
static const std::string chars("abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"1234567890"
"!@#$%^&*()"
"`~-_=+[{]{\\|;:'\",<.>/? ");
std::string result;
for (int i = 0; i < 20; i++) {
result += chars[dsn::rand::next_u32(chars.size())];
}
return result;
}
TEST(hotkey_collector_public_func_test, get_bucket_id_test)
{
int bucket_id = -1;
for (int i = 0; i < 1000000; i++) {
bucket_id = get_bucket_id(dsn::blob::create_from_bytes(generate_hash_key_by_random(false)),
FLAGS_hotkey_buckets_num);
ASSERT_GE(bucket_id, 0);
ASSERT_LT(bucket_id, FLAGS_hotkey_buckets_num);
}
}
TEST(hotkey_collector_public_func_test, find_outlier_index_test)
{
int threshold = 3;
int hot_index;
bool hot_index_found;
hot_index_found = find_outlier_index({1, 2, 3}, threshold, hot_index);
ASSERT_EQ(hot_index_found, false);
ASSERT_EQ(hot_index, -1);
hot_index_found = find_outlier_index({1, 2, 100000}, threshold, hot_index);
ASSERT_EQ(hot_index_found, true);
ASSERT_EQ(hot_index, 2);
hot_index_found = find_outlier_index({1, 10000, 2, 3, 4, 10000000, 6}, threshold, hot_index);
ASSERT_EQ(hot_index_found, true);
ASSERT_EQ(hot_index, 5);
hot_index_found = find_outlier_index(
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, threshold, hot_index);
ASSERT_EQ(hot_index_found, false);
ASSERT_EQ(hot_index, -1);
}
class coarse_collector_test : public pegasus_server_test_base
{
public:
coarse_collector_test() : coarse_collector(_server.get(), FLAGS_hotkey_buckets_num){};
hotkey_coarse_data_collector coarse_collector;
bool empty()
{
int empty = true;
for (const auto &iter : coarse_collector._hash_buckets) {
if (iter.load() != 0) {
return false;
}
}
return true;
}
dsn::task_tracker _tracker;
};
TEST_F(coarse_collector_test, coarse_collector)
{
detect_hotkey_result result;
for (int i = 0; i < 1000; i++) {
dsn::tasking::enqueue(LPC_WRITE, &_tracker, [&] {
dsn::blob hash_key =
dsn::blob::create_from_bytes(generate_hash_key_by_random(true, 80));
coarse_collector.capture_data(hash_key, 1);
});
}
coarse_collector.analyse_data(result);
ASSERT_NE(result.coarse_bucket_index, -1);
_tracker.wait_outstanding_tasks();
coarse_collector.clear();
ASSERT_TRUE(empty());
for (int i = 0; i < 1000; i++) {
dsn::tasking::enqueue(LPC_WRITE, &_tracker, [&] {
dsn::blob hash_key = dsn::blob::create_from_bytes(generate_hash_key_by_random(false));
coarse_collector.capture_data(hash_key, 1);
});
}
coarse_collector.analyse_data(result);
_tracker.wait_outstanding_tasks();
ASSERT_EQ(result.coarse_bucket_index, -1);
}
class fine_collector_test : public pegasus_server_test_base
{
public:
int max_queue_size = 1000;
int target_bucket_index = 0;
hotkey_fine_data_collector fine_collector;
fine_collector_test() : fine_collector(_server.get(), 1, max_queue_size)
{
fine_collector.change_target_bucket(0);
};
int now_queue_size()
{
int queue_size = 0;
std::pair<dsn::blob, uint64_t> key_weight_pair;
while (fine_collector._capture_key_queue.try_dequeue(key_weight_pair)) {
queue_size++;
};
return queue_size;
}
dsn::task_tracker _tracker;
};
TEST_F(fine_collector_test, fine_collector)
{
detect_hotkey_result result;
for (int i = 0; i < 1000; i++) {
dsn::tasking::enqueue(RPC_REPLICATION_WRITE_EMPTY, &_tracker, [&] {
dsn::blob hash_key =
dsn::blob::create_from_bytes(generate_hash_key_by_random(true, 80));
fine_collector.capture_data(hash_key, 1);
});
}
fine_collector.analyse_data(result);
_tracker.wait_outstanding_tasks();
ASSERT_EQ(result.hot_hash_key, "ThisisahotkeyThisisahotkey");
fine_collector.clear();
ASSERT_EQ(now_queue_size(), 0);
result.hot_hash_key = "";
for (int i = 0; i < 1000; i++) {
dsn::tasking::enqueue(RPC_REPLICATION_WRITE_EMPTY, &_tracker, [&] {
dsn::blob hash_key = dsn::blob::create_from_bytes(generate_hash_key_by_random(false));
fine_collector.capture_data(hash_key, 1);
});
}
fine_collector.analyse_data(result);
_tracker.wait_outstanding_tasks();
ASSERT_TRUE(result.hot_hash_key.empty());
for (int i = 0; i < 5000; i++) {
dsn::tasking::enqueue(RPC_REPLICATION_WRITE_EMPTY, &_tracker, [&] {
dsn::blob hash_key =
dsn::blob::create_from_bytes(generate_hash_key_by_random(true, 80));
fine_collector.capture_data(hash_key, 1);
});
}
_tracker.wait_outstanding_tasks();
ASSERT_LT(now_queue_size(), max_queue_size * 2);
}
class hotkey_collector_test : public pegasus_server_test_base
{
public:
hotkey_collector_test() { start(); }
std::shared_ptr<pegasus::server::hotkey_collector> get_read_collector()
{
return _server->_read_hotkey_collector;
}
std::shared_ptr<pegasus::server::hotkey_collector> get_write_collector()
{
return _server->_write_hotkey_collector;
}
dsn::replication::hotkey_type::type
get_collector_type(std::shared_ptr<pegasus::server::hotkey_collector> c)
{
return c->_hotkey_type;
}
hotkey_collector_state get_collector_stat(std::shared_ptr<pegasus::server::hotkey_collector> c)
{
return c->_state;
}
detect_hotkey_result *get_result(std::shared_ptr<pegasus::server::hotkey_collector> c)
{
return &c->_result;
}
void on_detect_hotkey(const dsn::replication::detect_hotkey_request &req,
dsn::replication::detect_hotkey_response &resp)
{
_server->on_detect_hotkey(req, resp);
}
get_rpc generate_get_rpc(std::string hash_key)
{
dsn::blob raw_key;
pegasus_generate_key(raw_key, hash_key, std::string("sortkey"));
get_rpc rpc(dsn::make_unique<dsn::blob>(raw_key), dsn::apps::RPC_RRDB_RRDB_GET);
return rpc;
}
dsn::apps::update_request generate_set_req(std::string hash_key)
{
dsn::apps::update_request req;
dsn::blob raw_key;
pegasus_generate_key(raw_key, hash_key, std::string("sortkey"));
req.key = raw_key;
req.value.assign(hash_key.c_str(), 0, hash_key.length());
return req;
}
dsn::replication::detect_hotkey_request
generate_control_rpc(dsn::replication::hotkey_type::type type,
dsn::replication::detect_action::type action)
{
dsn::replication::detect_hotkey_request req;
req.type = type;
req.action = action;
req.pid = dsn::gpid(0, 2);
return req;
}
dsn::task_tracker _tracker;
};
TEST_F(hotkey_collector_test, hotkey_type)
{
ASSERT_EQ(get_collector_type(get_read_collector()), dsn::replication::hotkey_type::READ);
ASSERT_EQ(get_collector_type(get_write_collector()), dsn::replication::hotkey_type::WRITE);
}
TEST_F(hotkey_collector_test, state_transform)
{
auto collector = get_read_collector();
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::STOPPED);
dsn::replication::detect_hotkey_response resp;
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::START),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::COARSE_DETECTING);
for (int i = 0; i < 100; i++) {
dsn::tasking::enqueue(LPC_WRITE, &_tracker, [&] {
_server->on_get(generate_get_rpc(generate_hash_key_by_random(true, 80)));
});
}
_tracker.wait_outstanding_tasks();
collector->analyse_data();
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::FINE_DETECTING);
for (int i = 0; i < 100; i++) {
dsn::tasking::enqueue(LPC_WRITE, &_tracker, [&] {
_server->on_get(generate_get_rpc(generate_hash_key_by_random(true, 80)));
});
}
_tracker.wait_outstanding_tasks();
collector->analyse_data();
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::FINISHED);
auto result = get_result(collector);
ASSERT_TRUE(result->if_find_result);
ASSERT_EQ(result->hot_hash_key, "ThisisahotkeyThisisahotkey");
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::QUERY),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
ASSERT_EQ(resp.hotkey_result, "ThisisahotkeyThisisahotkey");
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::STOP),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::STOPPED);
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::START),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::COARSE_DETECTING);
for (int i = 0; i < 1000; i++) {
dsn::tasking::enqueue(LPC_WRITE, &_tracker, [&] {
_server->on_get(generate_get_rpc(generate_hash_key_by_random(false)));
});
}
collector->analyse_data();
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::COARSE_DETECTING);
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::STOP),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
ASSERT_EQ(get_collector_stat(collector), hotkey_collector_state::STOPPED);
_tracker.wait_outstanding_tasks();
}
TEST_F(hotkey_collector_test, data_completeness)
{
dsn::replication::detect_hotkey_response resp;
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::START),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::WRITE,
dsn::replication::detect_action::START),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
const uint16_t WRITE_REQUEST_COUNT = 1000;
dsn::message_ex *writes[WRITE_REQUEST_COUNT];
for (int i = 0; i < WRITE_REQUEST_COUNT; i++) {
writes[i] = create_put_request(generate_set_req(std::to_string(i)));
}
_server->on_batched_write_requests(int64_t(0), uint64_t(0), writes, WRITE_REQUEST_COUNT);
for (int i = 0; i < WRITE_REQUEST_COUNT; i++) {
auto rpc = generate_get_rpc(std::to_string(i));
_server->on_get(rpc);
auto value = rpc.response().value.to_string();
ASSERT_EQ(value, std::to_string(i));
}
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::READ,
dsn::replication::detect_action::STOP),
resp);
ASSERT_EQ(resp.err, dsn::ERR_OK);
on_detect_hotkey(generate_control_rpc(dsn::replication::hotkey_type::WRITE,
dsn::replication::detect_action::STOP),
resp);
}
} // namespace server
} // namespace pegasus