Add testing for 'mysql-query_rules_fast_routing_algorithm'

- Improved test 'test_sqlite3_server_and_fast_routing-t' for performing
  the same checks with both supported values for the variable.
- Added new test 'test_query_rules_fast_routing_algorithm-t'
pull/4182/head
Javier Jaramago Fernández 3 years ago
parent abccb39201
commit e30c0e2a27

@ -0,0 +1,449 @@
/**
* @file test_query_rules_fast_routing_algorithm-t.cpp
* @brief This test performs the following checks:
* - That multiple 'rules_fast_routing' are being properly evaluated.
* - That 'mysql-query_rules_fast_routing_algorithm' properly controls from which hashmaps the query
* rules are searched.
* - That used memory increases/decreases as expected depending on the value selected for
* 'mysql-query_rules_fast_routing_algorithm'.
*/
#include <cstring>
#include <stdio.h>
#include <unistd.h>
#include <fstream>
#include <mysql.h>
#include <mysql/mysqld_error.h>
#include "tap.h"
#include "utils.h"
#include "command_line.h"
#include "json.hpp"
using std::pair;
using std::string;
using nlohmann::json;
using std::fstream;
using std::vector;
// Used for 'extract_module_host_port'
#include "modules_server_test.h"
// UTILS TAKEN FROM PR 4161 - TODO: REMOVE AFTER PR MERGED
///////////////////////////////////////////////////////////////////////////////
#include <regex>
string _get_env(const string& var) {
string f_path {};
char* p_infra_datadir = std::getenv(var.c_str());
if (p_infra_datadir != nullptr) {
f_path = p_infra_datadir;
}
return f_path;
}
int _open_file_and_seek_end(const string& f_path, std::fstream& f_stream) {
const char* c_f_path { f_path.c_str() };
f_stream.open(f_path.c_str(), std::fstream::in | std::fstream::out);
if (!f_stream.is_open() || !f_stream.good()) {
diag("Failed to open '%s' file: { path: %s, error: %d }", basename(c_f_path), c_f_path, errno);
return EXIT_FAILURE;
}
f_stream.seekg(0, std::ios::end);
return EXIT_SUCCESS;
}
using _line_match_t = std::tuple<std::fstream::pos_type, std::string, std::smatch>;
enum _LINE_MATCH_T { POS, LINE, MATCHES };
std::vector<_line_match_t> _get_matching_lines(std::fstream& f_stream, const std::string& regex) {
std::vector<_line_match_t> found_matches {};
std::string next_line {};
std::fstream::pos_type init_pos { f_stream.tellg() };
while (std::getline(f_stream, next_line)) {
std::regex regex_err_line { regex };
std::smatch match_results {};
if (std::regex_search(next_line, match_results, regex_err_line)) {
found_matches.push_back({ f_stream.tellg(), next_line, match_results });
}
}
if (found_matches.empty() == false) {
const std::string& last_match { std::get<_LINE_MATCH_T::LINE>(found_matches.back()) };
const std::fstream::pos_type last_match_pos { std::get<_LINE_MATCH_T::POS>(found_matches.back()) };
f_stream.clear(f_stream.rdstate() & ~std::ios_base::failbit);
f_stream.seekg(last_match_pos);
} else {
f_stream.clear(f_stream.rdstate() & ~std::ios_base::failbit);
f_stream.seekg(init_pos);
}
return found_matches;
}
///////////////////////////////////////////////////////////////////////////////
void parse_result_json_column(MYSQL_RES *result, json& j) {
if(!result) return;
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
j = json::parse(row[0]);
}
}
int extract_internal_session(MYSQL* proxy, nlohmann::json& j_internal_session) {
MYSQL_QUERY_T(proxy, "PROXYSQL INTERNAL SESSION");
MYSQL_RES* myres = mysql_store_result(proxy);
parse_result_json_column(myres, j_internal_session);
mysql_free_result(myres);
return EXIT_SUCCESS;
}
int get_query_int_res(MYSQL* admin, const string& q, int& val) {
MYSQL_QUERY_T(admin, q.c_str());
MYSQL_RES* myres = mysql_store_result(admin);
MYSQL_ROW myrow = mysql_fetch_row(myres);
int res = EXIT_FAILURE;
if (myrow && myrow[0]) {
char* p_end = nullptr;
val = std::strtol(myrow[0], &p_end, 10);
if (p_end == myrow[0]) {
diag("Failed to parse query result as 'int' - res: %s, query: %s", myrow[0], q.c_str());
} else {
res = EXIT_SUCCESS;
}
} else {
diag("Received empty result for query `%s`", q.c_str());
}
mysql_free_result(myres);
return res;
}
int extract_sess_qpo_dest_hg(MYSQL* proxy) {
json j_internal_session {};
int j_err = extract_internal_session(proxy, j_internal_session);
if (j_err) {
diag("Failed to extract and parse result from 'PROXYSQL INTERNAL SESSION'");
return -2;
}
int dest_hg = -2;
try {
dest_hg = j_internal_session["qpo"]["destination_hostgroup"];
} catch (const std::exception& e) {
diag("Processing of 'PROXYSQL INTERNAL SESSION' failed with exc: %s", e.what());
return -2;
}
return dest_hg;
}
int check_fast_routing_rules(MYSQL* proxy, uint32_t rng_init, uint32_t rng_end) {
for (uint32_t i = rng_init; i < rng_end; i += 2) {
const string schema { "randomschemaname" + std::to_string(i) };
diag("Changing schema to '%s'", schema.c_str());
if (mysql_select_db(proxy, schema.c_str())) {
fprintf(stdout, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
return EXIT_FAILURE;
}
diag("Issuing simple 'SELECT 1' to trigger WRITER rule for '%s'", schema.c_str());
MYSQL_QUERY_T(proxy, "SELECT 1");
mysql_free_result(mysql_store_result(proxy));
int dest_hg = extract_sess_qpo_dest_hg(proxy);
if (dest_hg == -2) {
return EXIT_FAILURE;
}
ok(i == dest_hg, "Destination hostgroup matches expected - Exp: %d, Act: %d", i, dest_hg);
diag("Issuing simple 'SELECT 2' to trigger READER rule for '%s'", schema.c_str());
MYSQL_QUERY_T(proxy, "SELECT 2");
mysql_free_result(mysql_store_result(proxy));
dest_hg = extract_sess_qpo_dest_hg(proxy);
if (dest_hg == -2) {
return EXIT_FAILURE;
}
ok(i + 1 == dest_hg, "Destination hostgroup matches expected - Exp: %d, Act: %d", i + 1, dest_hg);
}
return EXIT_SUCCESS;
};
int main(int argc, char** argv) {
// `5` logic checks + 20*3 checks per query rule, per test
plan((5 + 20*3) * 2);
CommandLine cl;
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
}
MYSQL* proxy = mysql_init(NULL);
MYSQL* admin = mysql_init(NULL);
if (!mysql_real_connect(proxy, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy));
return EXIT_FAILURE;
}
if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
return EXIT_FAILURE;
}
const auto create_mysql_servers_range = [] (
const CommandLine& cl, MYSQL* admin, const pair<string,int>& host_port, uint32_t rng_init, uint32_t rng_end
) -> int {
const string init { std::to_string(rng_init) };
const string end { std::to_string(rng_end) };
MYSQL_QUERY_T(admin, ("DELETE FROM mysql_servers WHERE hostgroup_id BETWEEN " + init + " AND " + end).c_str());
for (uint32_t i = rng_init; i < rng_end; i += 2) {
std::string q = "INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES ";
q += "(" + std::to_string(i) + ",'" + host_port.first + "'," + std::to_string(host_port.second) + ")";
q += ",";
q += "(" + std::to_string(i+1) + ",'" + host_port.first + "'," + std::to_string(host_port.second) + ")";
MYSQL_QUERY(admin, q.c_str());
}
return EXIT_SUCCESS;
};
const auto create_fast_routing_rules_range = [] (
const CommandLine& cl, MYSQL* admin, const pair<string,int>& host_port, uint32_t rng_init, uint32_t rng_end
) -> int {
const string init { std::to_string(rng_init) };
const string end { std::to_string(rng_end) };
MYSQL_QUERY_T(admin, ("DELETE FROM mysql_query_rules_fast_routing WHERE destination_hostgroup BETWEEN " + init + " AND " + end).c_str());
for (uint32_t i = rng_init; i < rng_end; i += 2) {
const string schema { "randomschemaname" + std::to_string(i) + "" };
const string user { cl.username };
string q = "INSERT INTO mysql_query_rules_fast_routing (username, schemaname, flagIN, destination_hostgroup, comment) VALUES ";
q += "('" + user + "', '" + schema + "' , 0, " + std::to_string(i) + ", 'writer" + std::to_string(i) + "'),";
q += "('" + user + "', '" + schema + "' , 1, " + std::to_string(i+1) + ", 'reader" + std::to_string(i+1) + "')";
MYSQL_QUERY(admin, q.c_str());
}
return EXIT_SUCCESS;
};
const auto test_fast_routing_algorithm = [&create_mysql_servers_range, &create_fast_routing_rules_range] (
const CommandLine& cl, MYSQL* admin, MYSQL* proxy, const pair<string,int>& host_port, fstream& errlog, int init_algo, int new_algo
) {
uint32_t rng_init = 1000;
uint32_t rng_end = 1020;
const char query_rules_mem_stats_query[] {
"SELECT variable_value FROM stats_memory_metrics WHERE variable_name='mysql_query_rules_memory'"
};
// Enable Admin debug and increase verbosity for Query_Processor
MYSQL_QUERY_T(admin, "SET admin-debug=1");
MYSQL_QUERY_T(admin, "LOAD ADMIN VARIABLES TO RUNTIME");
MYSQL_QUERY_T(admin, "UPDATE debug_levels SET verbosity=7 WHERE module='debug_mysql_query_processor'");
MYSQL_QUERY_T(admin, "LOAD DEBUG TO RUNTIME");
int c_err = create_mysql_servers_range(cl, admin, host_port, rng_init, rng_end);
if (c_err) {
return EXIT_FAILURE;
}
MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME");
printf("\n");
diag("Testing 'query_rules_fast_routing_algorithm=%d'", init_algo);
MYSQL_QUERY_T(admin, ("SET mysql-query_rules_fast_routing_algorithm=" + std::to_string(init_algo)).c_str());
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
// Always cleanup the rules before the test to get proper memory usage diff
MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules_fast_routing");
MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
int init_rules_mem_stats = -1;
int get_mem_stats_err = get_query_int_res(admin, query_rules_mem_stats_query, init_rules_mem_stats);
if (get_mem_stats_err) {
return EXIT_FAILURE;
}
diag("Initial 'mysql_query_rules_memory' of '%d'", init_rules_mem_stats);
// Check that fast_routing rules are being properly triggered
c_err = create_fast_routing_rules_range(cl, admin, host_port, rng_init, rng_end);
if (c_err) {
return EXIT_FAILURE;
}
MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
// Seek end of file for error log
errlog.seekg(0, std::ios::end);
// Check that fast_routing rules are properly working for the defined range
check_fast_routing_rules(proxy, rng_init, rng_end);
// Give some time for the error log to be written
usleep(100*1000);
const string init_algo_scope { init_algo == 1 ? "thread-local" : "global" };
const string init_search_regex { "Searching " + init_algo_scope + " 'rules_fast_routing' hashmap" };
vector<_line_match_t> matched_lines { _get_matching_lines(errlog, init_search_regex) };
ok(
matched_lines.size() == rng_end - rng_init,
"Number of '%s' searchs in error log should match issued queries - Exp: %d, Act: %ld",
init_algo_scope.c_str(), rng_end - rng_init, matched_lines.size()
);
printf("\n");
int old_mem_stats = -1;
get_mem_stats_err = get_query_int_res(admin, query_rules_mem_stats_query, old_mem_stats);
if (get_mem_stats_err) {
return EXIT_FAILURE;
}
// Changing the algorithm shouldn't have any effect
diag("Testing 'query_rules_fast_routing_algorithm=%d'", new_algo);
MYSQL_QUERY_T(admin, ("SET mysql-query_rules_fast_routing_algorithm=" + std::to_string(new_algo)).c_str());
MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
errlog.seekg(0, std::ios::end);
diag("Search should still be performed 'per-thread'. Only variable has changed.");
check_fast_routing_rules(proxy, rng_init, rng_end);
matched_lines = _get_matching_lines(errlog, init_search_regex);
// Give some time for the error log to be written
usleep(100*1000);
ok(
matched_lines.size() == rng_end - rng_init,
"Number of 'thread-local' searchs in error log should match issued queries - Exp: %d, Act: %ld",
rng_end - rng_init, matched_lines.size()
);
int new_mem_stats = -1;
get_mem_stats_err = get_query_int_res(admin, query_rules_mem_stats_query, new_mem_stats);
if (get_mem_stats_err) {
return EXIT_FAILURE;
}
diag("Memory SHOULDN'T have changed just because of a variable change");
ok(
old_mem_stats - init_rules_mem_stats == new_mem_stats - init_rules_mem_stats,
"Memory stats shouldn't increase just by the variable change - old: %d, new: %d",
old_mem_stats - init_rules_mem_stats, new_mem_stats - init_rules_mem_stats
);
printf("\n");
MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
diag("Search should now be using the per thread-maps");
// Seek end of file for error log
errlog.seekg(0, std::ios::end);
check_fast_routing_rules(proxy, rng_init, rng_end);
// Give some time for the error log to be written
usleep(100*1000);
const string new_algo_scope { new_algo == 1 ? "thread-local" : "global" };
const string new_search_regex { "Searching " + new_algo_scope + " 'rules_fast_routing' hashmap" };
vector<_line_match_t> global_matched_lines { _get_matching_lines(errlog, new_search_regex) };
ok(
global_matched_lines.size() == rng_end - rng_init,
"Number of '%s' searchs in error log should match issued queries - Exp: %d, Act: %ld",
new_algo_scope.c_str(), rng_end - rng_init, global_matched_lines.size()
);
get_mem_stats_err = get_query_int_res(admin, query_rules_mem_stats_query, new_mem_stats);
if (get_mem_stats_err) {
return EXIT_FAILURE;
}
bool mem_check_res = false;
string exp_change { "" };
if (init_algo == 1 && new_algo == 2) {
mem_check_res = (old_mem_stats - init_rules_mem_stats) > (new_mem_stats - init_rules_mem_stats);
exp_change = "decrease";
} else if (init_algo == 2 && new_algo == 1) {
mem_check_res = (old_mem_stats - init_rules_mem_stats) < (new_mem_stats - init_rules_mem_stats);
exp_change = "increase";
} else {
mem_check_res = (old_mem_stats - init_rules_mem_stats) == (new_mem_stats - init_rules_mem_stats);
exp_change = "not change";
}
ok(
mem_check_res,
"Memory stats should %s after 'LOAD MYSQL QUERY RULES TO RUNTIME' - old: %d, new: %d",
exp_change.c_str(), (old_mem_stats - init_rules_mem_stats), (new_mem_stats - init_rules_mem_stats)
);
return EXIT_SUCCESS;
};
pair<string,int> host_port {};
int host_port_err = extract_module_host_port(admin, "sqliteserver-mysql_ifaces", host_port);
if (host_port_err) {
goto cleanup;
}
MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules");
MYSQL_QUERY_T(admin, "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, flagOUT, cache_ttl) VALUES (1,1,'^SELECT 1$', 0, 600000)");
MYSQL_QUERY_T(admin, "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, flagOUT, cache_ttl) VALUES (2,1,'^SELECT 2$', 1, 600000)");
MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules_fast_routing");
MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
{
const string f_path { _get_env("REGULAR_INFRA_DATADIR") + "/proxysql.log" };
fstream errlog {};
int of_err = _open_file_and_seek_end(f_path, errlog);
if (of_err) {
diag("Failed to open ProxySQL log file. Aborting further testing...");
goto cleanup;
}
int test_err = test_fast_routing_algorithm(cl, admin, proxy, host_port, errlog, 1, 2);
if (test_err) { goto cleanup; }
test_err = test_fast_routing_algorithm(cl, admin, proxy, host_port, errlog, 2, 1);
if (test_err) { goto cleanup; }
}
cleanup:
mysql_close(proxy);
mysql_close(admin);
return exit_status();
}

@ -26,45 +26,17 @@ inline unsigned long long monotonic_time() {
return (((unsigned long long) ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000);
}
int main(int argc, char** argv) {
CommandLine cl;
int benchmark_query_rules_fast_routing(CommandLine& cl, MYSQL* proxysql_admin, MYSQL* proxysql_mysql) {
std::string s;
MYSQL * proxysql_mysql = mysql_init(NULL);
std::pair<std::string, int> host_port {};
int host_port_err;
MYSQL* proxysql_admin = mysql_init(NULL);
double nofr = 0;
double fr = 0;
plan(1);
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
goto cleanup;
}
// Connect to ProxySQL Admin and check current SQLite3 configuration
if (
!mysql_real_connect(
proxysql_admin, cl.host, cl.admin_username, cl.admin_password,
NULL, cl.admin_port, NULL, 0
)
) {
fprintf(
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
mysql_error(proxysql_admin)
);
goto cleanup;
}
// {
host_port_err = extract_module_host_port(proxysql_admin, "sqliteserver-mysql_ifaces", host_port);
int host_port_err = extract_module_host_port(proxysql_admin, "sqliteserver-mysql_ifaces", host_port);
if (host_port_err) {
diag("Failed to get and parse 'sqliteserver-mysql_ifaces' at line '%d'", __LINE__);
goto cleanup;
return EXIT_FAILURE;
}
s = "DELETE FROM mysql_servers WHERE hostgroup_id BETWEEN 1001 AND 3000";
diag("Executing: %s", s.c_str());
@ -103,7 +75,7 @@ int main(int argc, char** argv) {
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
mysql_error(proxysql_mysql)
);
goto cleanup;
return EXIT_FAILURE;
}
{
unsigned long long begin;
@ -114,7 +86,7 @@ int main(int argc, char** argv) {
rc = mysql_query(proxysql_mysql, "SELECT 1");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
MYSQL_RES* result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
@ -143,7 +115,7 @@ int main(int argc, char** argv) {
rc = mysql_query(proxysql_mysql, "SELECT 1");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
MYSQL_RES* result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
@ -175,14 +147,14 @@ int main(int argc, char** argv) {
rc = mysql_query(proxysql_mysql, "SELECT 1");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
MYSQL_RES* result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
rc = mysql_query(proxysql_mysql, "SELECT 2");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
@ -212,14 +184,14 @@ int main(int argc, char** argv) {
rc = mysql_query(proxysql_mysql, "SELECT 1");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
MYSQL_RES* result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
rc = mysql_query(proxysql_mysql, "SELECT 2");
if (rc != 0) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
goto cleanup;
return EXIT_FAILURE;
}
result = mysql_store_result(proxysql_mysql);
mysql_free_result(result);
@ -229,6 +201,49 @@ int main(int argc, char** argv) {
fr += (end - begin);
}
ok (fr < (nofr * 3) , "Times for: Single HG = %dms , multi HG = %dms", (int)(nofr/1000), (int)(fr/1000));
return EXIT_SUCCESS;
}
int main(int argc, char** argv) {
CommandLine cl;
MYSQL * proxysql_mysql = mysql_init(NULL);
MYSQL* proxysql_admin = mysql_init(NULL);
plan(2);
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
goto cleanup;
}
// Connect to ProxySQL Admin and check current SQLite3 configuration
if (
!mysql_real_connect(
proxysql_admin, cl.host, cl.admin_username, cl.admin_password,
NULL, cl.admin_port, NULL, 0
)
) {
fprintf(
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
mysql_error(proxysql_admin)
);
goto cleanup;
}
MYSQL_QUERY(proxysql_admin, "SET mysql-query_rules_fast_routing_algorithm=1");
MYSQL_QUERY(proxysql_admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
benchmark_query_rules_fast_routing(cl, proxysql_admin, proxysql_mysql);
MYSQL_QUERY(proxysql_admin, "SET mysql-query_rules_fast_routing_algorithm=2");
MYSQL_QUERY(proxysql_admin, "LOAD MYSQL QUERY RULES TO RUNTIME");
mysql_close(proxysql_mysql);
proxysql_mysql = mysql_init(NULL);
benchmark_query_rules_fast_routing(cl, proxysql_admin, proxysql_mysql);
cleanup:
mysql_close(proxysql_admin);

Loading…
Cancel
Save