Niclas Antti f62d155036 MXS-173 Test for throttling filter
The test may fail if the client/maxscale/mariadb combo is too slow.
TODO, maybe: today I saw mysql_query() hang (in a poll) when maxscale
disconnected. Is there something that can be done about that?
I added a source directory, base, for stuff that should become part of a common
utility library in the future.
2018-05-25 14:51:36 +03:00

237 lines
7.1 KiB
C++

/*
* MXS-173 throttling filter
*
*/
#include "testconnections.h"
#include <base/stopwatch.h>
#include <base/appexception.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <cmath>
DEFINE_EXCEPTION(Whoopsy);
// TODO these should be read from maxscale.cnf. Maybe the test-lib should replace
// any "###ENV_VAR###", with environment variables so that code and conf can share.
constexpr int max_qps = 1000;
constexpr float throttling_duration = 10000 / 1000.0;
constexpr float sampling_duration = 250 / 1000.0;
constexpr float continuous_duration = 2000 / 1000.0;
constexpr int NUM_ROWS = 100000;
void create_table(MYSQL* conn)
{
if (execute_query_silent(conn, "drop table if exists test.throttle;"
"create table test.throttle(id int, name varchar(30),"
"primary key(id));"))
{
THROW(Whoopsy, "Create table failed - could not start test");
}
}
void insert_rows(MYSQL* conn)
{
std::ostringstream os;
os << "insert into throttle values\n";
for (int i = 0; i < NUM_ROWS; ++i)
{
if (i != 0)
{
os << ',';
}
os << '(' << i << ", '"
<< std::to_string(i) << "')\n";
}
os << ';';
if (execute_query_silent(conn, os.str().c_str()), false)
{
THROW(Whoopsy, "Inserts failed - could not start test");
}
}
struct ReadSpeed
{
bool error;
base::Duration duration;
float qps;
};
ReadSpeed read_rows(MYSQL* conn, int num_rows, int max_qps, bool expect_error)
{
base::StopWatch sw;
for (int i = 0; i < num_rows; ++i)
{
int index = rand() % NUM_ROWS;
std::ostringstream os;
os << "select name from test.throttle where id=" << index;
if (mysql_query(conn, os.str().c_str()))
{
if (expect_error)
{
base::Duration dur = sw.lap();
std::cout << "\r \r";
return {true, dur, i / std::chrono::duration<float>(dur).count()};
}
else
{
THROW(Whoopsy, "Unexpected error while reading rows.");
}
}
// Maybe a create function template make_unique_deleter. Should work for most cases.
// But it would be safer with one regular function per type/function pair, returning
// a unique pointer.
std::unique_ptr<MYSQL_RES, void(*)(MYSQL_RES*)> result(mysql_store_result(conn),
mysql_free_result);
if (!result)
{
THROW(Whoopsy, "No resultset for index=" << index);
}
MYSQL_ROW row = mysql_fetch_row(&*result);
if (row)
{
if (std::stoi(row[0]) != index)
{
THROW(Whoopsy, "Differing values index=" << index << " name=" << row[0]);
}
}
else
{
THROW(Whoopsy, "Row id = " << index << " not in resultset.");
}
if ((row = mysql_fetch_row(&*result)))
{
THROW(Whoopsy, "Extra row index = " << index << " name = " << row[0] << " in resultset.");
}
std::cout << "\r" << num_rows - i - 1 << ' ';
if (i % 117 == 0)
{
std::cout.flush();
}
}
base::Duration dur = sw.lap();
std::cout << "\r \r";
return ReadSpeed {false, dur, num_rows / std::chrono::duration<float>(dur).count()};
}
void gauge_raw_speed(TestConnections& test)
{
const int raw_rows = NUM_ROWS / 5;
std::cout << "\n****\nRead " << raw_rows
<< " rows via master readconnrouter, to gauge speed.\n";
auto rs = read_rows(test.maxscales->conn_master[0], raw_rows, 0, false);
std::cout << rs.qps << "qps " << " duration " << rs.duration << '\n';
if (rs.qps < 2 * max_qps)
{
std::ostringstream os;
os << "The raw speed is too slow, " << rs.qps
<< "qps, compared to max_qps = " << max_qps << "qps for accurate testing.";
test.add_result(1, os.str().c_str());
}
}
void verify_throttling_performace(TestConnections& test)
{
int three_quarter = 3 * max_qps * throttling_duration / 4;
std::cout << "\n****\nRead " << three_quarter
<< " rows which should take about " << 3 * throttling_duration / 4
<< " seconds.\nThrottling should keep qps around "
<< max_qps << ".\n";
auto rs1 = read_rows(test.maxscales->conn_rwsplit[0], three_quarter, 0, false);
std::cout << "1: " << rs1.qps << "qps " << " duration " << rs1.duration << '\n';
std::cout << "Sleep for " << continuous_duration << "s (continuous_duration)\n";
usleep(continuous_duration * 1000000);
std::cout << "Run the same read again. Should be throttled, but not disconnected.\n";
auto rs2 = read_rows(test.maxscales->conn_rwsplit[0], three_quarter, 0, false);
std::cout << "2: " << rs2.qps << "qps " << " duration " << rs2.duration << '\n';
if (std::abs(rs1.qps - max_qps) > 0.1 * max_qps ||
std::abs(rs2.qps - max_qps) > 0.1 * max_qps)
{
std::ostringstream os;
os << "Throttled speed 1: " << rs1.qps << " or 2: " << rs2.qps <<
"differs from max_qps " << max_qps << " by more than 10%%";
test.add_result(1, os.str().c_str());
}
}
void verify_throttling_disconnect(TestConnections& test)
{
int half_rows = max_qps * throttling_duration / 2;
std::cout << "\n****\nRead " << 3 * half_rows
<< " rows which should cause a disconnect at a little\nbelow "
<< half_rows << " rows to go, in about " << throttling_duration << "s.\n";
auto rs = read_rows(test.maxscales->conn_rwsplit[0], 3 * half_rows, 0, true);
std::cout << rs.qps << "qps " << " duration " << rs.duration << '\n';
if (!rs.error)
{
std::ostringstream os;
os << "Throttle filter did not disconnect rogue session.\n"
<< rs.qps << "qps " << " duration " << rs.duration;
test.add_result(1, os.str().c_str());
}
if (std::abs(rs.qps - max_qps) > 0.1 * max_qps)
{
std::ostringstream os;
os << "Throttled speed " << rs.qps << " differs from max_qps " << max_qps
<< " by more than 10%%";
test.add_result(1, os.str().c_str());
}
}
int main(int argc, char *argv[])
{
srand(clock());
TestConnections test{argc, argv};
try
{
test.print_env();
test.maxscales->connect_maxscale(0);
std::cout << "Create table\n";
create_table(test.maxscales->conn_master[0]);
std::cout << "Insert rows\n";
insert_rows(test.maxscales->conn_master[0]);
gauge_raw_speed(test);
test.repl->sync_slaves();
verify_throttling_performace(test);
test.maxscales->close_maxscale_connections(0);
test.maxscales->connect_maxscale(0);
verify_throttling_disconnect(test);
std::cout << "\n\n";
}
catch (std::exception& ex)
{
test.add_result(1, ex.what());
}
catch (...)
{
test.add_result(1, "catch ...");
throw;
}
return test.global_result;
}