| /* |
| * 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(); |
| } |
| } |