blob: 75b631ef0e3e34b06ba220790f16290d5f110ed0 [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 <vector>
#include <bitset>
#include <thread>
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <chrono>
#include <thread>
#include <atomic>
#include <memory>
#include <sys/time.h>
#include <dsn/service_api_cpp.h>
#include "pegasus/client.h"
#include "data_verifier.h"
using namespace std;
using namespace ::pegasus;
static pegasus_client *client = nullptr;
static string app_name;
static string pegasus_cluster_name;
static uint32_t set_and_get_timeout_milliseconds;
static int set_thread_count = 0;
static std::atomic_llong set_next(0);
static int get_thread_count = 0;
static std::vector<long long> set_thread_setting_id;
static const char *set_next_key = "set_next";
static const char *check_max_key = "check_max";
static const char *hash_key_prefix = "kill_test_hash_key_";
static const char *sort_key_prefix = "kill_test_sort_key_";
static const char *value_prefix = "kill_test_value_";
static const long stat_batch = 100000;
static const long stat_min_pos = 0;
static const long stat_p90_pos = stat_batch - stat_batch / 10 - 1;
static const long stat_p99_pos = stat_batch - stat_batch / 100 - 1;
static const long stat_p999_pos = stat_batch - stat_batch / 1000 - 1;
static const long stat_p9999_pos = stat_batch - stat_batch / 10000 - 1;
static const long stat_max_pos = stat_batch - 1;
// return time in us.
long get_time()
{
struct timeval tv;
gettimeofday(&tv, nullptr);
return tv.tv_sec * 1000000 + tv.tv_usec;
}
long long get_min_thread_setting_id()
{
long long id = set_thread_setting_id[0];
for (int i = 1; i < set_thread_count; ++i) {
if (set_thread_setting_id[i] < id)
id = set_thread_setting_id[i];
}
return id;
}
void do_set(int thread_id)
{
char buf[1024];
std::string hash_key;
std::string sort_key;
std::string value;
long long id = 0;
int try_count = 0;
long stat_count = 0;
std::vector<long> stat_time;
stat_time.resize(stat_batch);
long last_time = get_time();
while (true) {
if (try_count == 0) {
id = set_next++;
set_thread_setting_id[thread_id] = id;
sprintf(buf, "%s%lld", hash_key_prefix, id);
hash_key.assign(buf);
sprintf(buf, "%s%lld", sort_key_prefix, id);
sort_key.assign(buf);
sprintf(buf, "%s%lld", value_prefix, id);
value.assign(buf);
}
pegasus_client::internal_info info;
int ret =
client->set(hash_key, sort_key, value, set_and_get_timeout_milliseconds, 0, &info);
if (ret == PERR_OK) {
long cur_time = get_time();
ddebug("SetThread[%d]: set succeed: id=%lld, try=%d, time=%ld (gpid=%d.%d, "
"decree=%lld, server=%s)",
thread_id,
id,
try_count,
(cur_time - last_time),
info.app_id,
info.partition_index,
info.decree,
info.server.c_str());
stat_time[stat_count++] = cur_time - last_time;
if (stat_count == stat_batch) {
std::sort(stat_time.begin(), stat_time.end());
long total_time = 0;
for (auto t : stat_time)
total_time += t;
ddebug("SetThread[%d]: set statistics: count=%lld, min=%lld, P90=%lld, P99=%lld, "
"P999=%lld, P9999=%lld, max=%lld, avg=%lld",
thread_id,
stat_count,
stat_time[stat_min_pos],
stat_time[stat_p90_pos],
stat_time[stat_p99_pos],
stat_time[stat_p999_pos],
stat_time[stat_p9999_pos],
stat_time[stat_max_pos],
total_time / stat_batch);
stat_count = 0;
}
last_time = cur_time;
try_count = 0;
} else {
derror("SetThread[%d]: set failed: id=%lld, try=%d, ret=%d, error=%s (gpid=%d.%d, "
"decree=%lld, server=%s)",
thread_id,
id,
try_count,
ret,
client->get_error_string(ret),
info.app_id,
info.partition_index,
info.decree,
info.server.c_str());
try_count++;
if (try_count > 3) {
sleep(1);
}
}
}
}
// for each round:
// - loop from range [start_id, end_id]
void do_get_range(int thread_id, int round_id, long long start_id, long long end_id)
{
ddebug(
"GetThread[%d]: round(%d): start get range [%u,%u]", thread_id, round_id, start_id, end_id);
char buf[1024];
std::string hash_key;
std::string sort_key;
std::string value;
long long id = start_id;
int try_count = 0;
long stat_count = 0;
std::vector<long> stat_time;
stat_time.resize(stat_batch);
long last_time = get_time();
while (id <= end_id) {
if (try_count == 0) {
sprintf(buf, "%s%lld", hash_key_prefix, id);
hash_key.assign(buf);
sprintf(buf, "%s%lld", sort_key_prefix, id);
sort_key.assign(buf);
sprintf(buf, "%s%lld", value_prefix, id);
value.assign(buf);
}
pegasus_client::internal_info info;
std::string get_value;
int ret =
client->get(hash_key, sort_key, get_value, set_and_get_timeout_milliseconds, &info);
if (ret == PERR_OK || ret == PERR_NOT_FOUND) {
long cur_time = get_time();
if (ret == PERR_NOT_FOUND) {
dfatal("GetThread[%d]: round(%d): get not found: id=%lld, try=%d, time=%ld "
"(gpid=%d.%d, server=%s), and exit",
thread_id,
round_id,
id,
try_count,
(cur_time - last_time),
info.app_id,
info.partition_index,
info.server.c_str());
exit(-1);
} else if (value != get_value) {
dfatal("GetThread[%d]: round(%d): get mismatched: id=%lld, try=%d, time=%ld, "
"expect_value=%s, real_value=%s (gpid=%d.%d, server=%s), and exit",
thread_id,
round_id,
id,
try_count,
(cur_time - last_time),
value.c_str(),
get_value.c_str(),
info.app_id,
info.partition_index,
info.server.c_str());
exit(-1);
} else {
dinfo("GetThread[%d]: round(%d): get succeed: id=%lld, try=%d, time=%ld "
"(gpid=%d.%d, server=%s)",
thread_id,
round_id,
id,
try_count,
(cur_time - last_time),
info.app_id,
info.partition_index,
info.server.c_str());
stat_time[stat_count++] = cur_time - last_time;
if (stat_count == stat_batch) {
std::sort(stat_time.begin(), stat_time.end());
long total_time = 0;
for (auto t : stat_time)
total_time += t;
ddebug("GetThread[%d]: get statistics: count=%lld, min=%lld, P90=%lld, "
"P99=%lld, P999=%lld, P9999=%lld, max=%lld, avg=%lld",
thread_id,
stat_count,
stat_time[stat_min_pos],
stat_time[stat_p90_pos],
stat_time[stat_p99_pos],
stat_time[stat_p999_pos],
stat_time[stat_p9999_pos],
stat_time[stat_max_pos],
total_time / stat_batch);
stat_count = 0;
}
}
last_time = cur_time;
try_count = 0;
id++;
} else {
derror("GetThread[%d]: round(%d): get failed: id=%lld, try=%d, ret=%d, error=%s "
"(gpid=%d.%d, server=%s)",
thread_id,
round_id,
id,
try_count,
ret,
client->get_error_string(ret),
info.app_id,
info.partition_index,
info.server.c_str());
try_count++;
if (try_count > 3) {
sleep(1);
}
}
}
ddebug("GetThread[%d]: round(%d): finish get range [%u,%u]",
thread_id,
round_id,
start_id,
end_id);
}
void do_check(int thread_count)
{
int round_id = 1;
while (true) {
long long range_end = get_min_thread_setting_id() - 1;
if (range_end < thread_count) {
sleep(1);
continue;
}
ddebug("CheckThread: round(%d): start check round, range_end=%lld", round_id, range_end);
long start_time = get_time();
std::vector<std::thread> worker_threads;
long long piece_count = range_end / thread_count;
for (int i = 0; i < thread_count; ++i) {
long long start_id = piece_count * i;
long long end_id = (i == thread_count - 1) ? range_end : (piece_count * (i + 1) - 1);
worker_threads.emplace_back(do_get_range, i, round_id, start_id, end_id);
}
for (auto &t : worker_threads) {
t.join();
}
long finish_time = get_time();
ddebug("CheckThread: round(%d): finish check round, range_end=%lld, total_time=%ld seconds",
round_id,
range_end,
(finish_time - start_time) / 1000000);
// update check_max
while (true) {
char buf[1024];
sprintf(buf, "%lld", range_end);
int ret = client->set(check_max_key, "", buf, set_and_get_timeout_milliseconds);
if (ret == PERR_OK) {
ddebug("CheckThread: round(%d): update \"%s\" succeed: check_max=%lld",
round_id,
check_max_key,
range_end);
break;
} else {
derror("CheckThread: round(%d): update \"%s\" failed: check_max=%lld, ret=%d, "
"error=%s",
round_id,
check_max_key,
range_end,
ret,
client->get_error_string(ret));
}
}
round_id++;
}
}
void do_mark()
{
char buf[1024];
long last_time = get_time();
long long old_id = 0;
std::string value;
while (true) {
sleep(1);
long long new_id = get_min_thread_setting_id();
dassert(new_id >= old_id, "%" PRId64 " VS %" PRId64 "", new_id, old_id);
if (new_id == old_id) {
continue;
}
sprintf(buf, "%lld", new_id);
value.assign(buf);
int ret = client->set(set_next_key, "", value, set_and_get_timeout_milliseconds);
if (ret == PERR_OK) {
long cur_time = get_time();
ddebug("MarkThread: update \"%s\" succeed: set_next=%lld, time=%ld",
set_next_key,
new_id,
(cur_time - last_time));
old_id = new_id;
} else {
derror("MarkThread: update \"%s\" failed: set_next=%lld, ret=%d, error=%s",
set_next_key,
new_id,
ret,
client->get_error_string(ret));
}
}
}
void verifier_initialize(const char *config_file)
{
const char *section = "pegasus.killtest";
if (!pegasus_client_factory::initialize(config_file)) {
exit(-1);
}
app_name = dsn_config_get_value_string(
section, "verify_app_name", "temp", "verify app name"); // default using temp
pegasus_cluster_name =
dsn_config_get_value_string(section, "pegasus_cluster_name", "", "pegasus cluster name");
if (pegasus_cluster_name.empty()) {
derror("Should config the cluster name for verifier");
exit(-1);
}
client = pegasus_client_factory::get_client(pegasus_cluster_name.c_str(), app_name.c_str());
if (client == nullptr) {
derror("Initialize the _client failed");
exit(-1);
}
set_and_get_timeout_milliseconds = (uint32_t)dsn_config_get_value_uint64(
section, "set_and_get_timeout_milliseconds", 3000, "set and get timeout milliseconds");
set_thread_count =
(uint32_t)dsn_config_get_value_uint64(section, "set_thread_count", 5, "set thread count");
get_thread_count = (uint32_t)dsn_config_get_value_uint64(
section, "get_thread_count", set_thread_count * 4, "get thread count");
}
void verifier_start()
{
// check the set_next
while (true) {
std::string set_next_value;
int ret = client->get(set_next_key, "", set_next_value, set_and_get_timeout_milliseconds);
if (ret == PERR_OK) {
long long i = atoll(set_next_value.c_str());
if (i == 0 && !set_next_value.empty()) {
derror("MainThread: read \"%s\" failed: value_str=%s",
set_next_key,
set_next_value.c_str());
exit(-1);
}
ddebug("MainThread: read \"%s\" succeed: value=%lld", set_next_key, i);
set_next.store(i);
break;
} else if (ret == PERR_NOT_FOUND) {
ddebug("MainThread: read \"%s\" not found, init set_next to 0", set_next_key);
set_next.store(0);
break;
} else {
derror("MainThread: read \"%s\" failed: error=%s",
set_next_key,
client->get_error_string(ret));
}
}
set_thread_setting_id.resize(set_thread_count);
std::vector<std::thread> set_threads;
for (int i = 0; i < set_thread_count; ++i) {
set_threads.emplace_back(do_set, i);
}
std::thread mark_thread(do_mark);
// start several threads to read data from pegasus cluster and check data correctness,
// block until the check failed
do_check(get_thread_count);
mark_thread.join();
for (auto &t : set_threads) {
t.join();
}
}