From 1fdaad02828777c500d325418445f83ea3aed961 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?=
 <jaramago.fernandez.javier@gmail.com>
Date: Fri, 2 Aug 2024 14:28:41 +0200
Subject: [PATCH] Add TAP test checking thread conns distribution

---
 test/tap/tests/test_thread_conn_dist-t.cpp | 132 +++++++++++++++++++++
 1 file changed, 132 insertions(+)
 create mode 100644 test/tap/tests/test_thread_conn_dist-t.cpp

diff --git a/test/tap/tests/test_thread_conn_dist-t.cpp b/test/tap/tests/test_thread_conn_dist-t.cpp
new file mode 100644
index 0000000000..047498edc0
--- /dev/null
+++ b/test/tap/tests/test_thread_conn_dist-t.cpp
@@ -0,0 +1,132 @@
+#include <chrono>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+#include "mysql.h"
+#include "json.hpp"
+
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+using nlohmann::json;
+
+#define TAP_NAME "TAP_THREAD_CONN_DIST___"
+
+const int TEST_DURATION_SEC = get_env_int(TAP_NAME"TEST_DURATION_SEC", 20);
+const int ITER_CONN_COUNT = get_env_int(TAP_NAME"ITER_CONN_COUNT", 256);
+
+void incr_proc_limits(uint32_t MAX_CONN_COUNT) {
+	diag("Elevating process limits if required for conns creation");
+
+	struct rlimit limits { 0, 0 };
+	getrlimit(RLIMIT_NOFILE, &limits);
+	diag("Old process limits   rlim_cur=%ld rlim_max=%ld", limits.rlim_cur, limits.rlim_max);
+
+	if (limits.rlim_cur < MAX_CONN_COUNT * 2) {
+		diag("Updating process max FD limit");
+		limits.rlim_cur = MAX_CONN_COUNT * 2;
+		setrlimit(RLIMIT_NOFILE, &limits);
+	}
+
+	diag("New process limits   rlim_cur=%ld rlim_max=%ld", limits.rlim_cur, limits.rlim_max);
+}
+
+pair<uint32_t,vector<MYSQL*>> create_frontend_conns(CommandLine& cl, uint32_t CONNS_TOTAL) {
+	vector<MYSQL*> conns {};
+
+	for (int i = 0; i < CONNS_TOTAL; i++) {
+		MYSQL* myconn = mysql_init(NULL);
+
+		if (!mysql_real_connect(myconn, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
+			diag(
+				"Failed to connect   addr=%s port=%d user=%s pass=%s err=%s",
+				cl.host, cl.port, cl.username, cl.password, mysql_error(myconn)
+			);
+			return { EXIT_FAILURE, {} };
+		}
+
+		conns.push_back(myconn);
+	}
+
+	return { EXIT_SUCCESS, conns };
+}
+
+void check_thread_conn_dist(const map<string,vector<MYSQL*>>& m_thread_conns) {
+	size_t lo_count = 0;
+	size_t hg_count = 0;
+
+	diag("Dumping per-thread conn count:");
+	for (const pair<string,vector<MYSQL*>>& thread_conns : m_thread_conns) {
+		if (lo_count == 0 || thread_conns.second.size() < lo_count) {
+			lo_count = thread_conns.second.size();
+		}
+		if (hg_count == 0 || thread_conns.second.size() > hg_count) {
+			hg_count = thread_conns.second.size();
+		}
+		fprintf(stderr, "Map entry   thread=%s count=%ld\n", thread_conns.first.c_str(), thread_conns.second.size());
+	}
+
+	ok(
+		hg_count / 2 < lo_count,
+		"Half the highest conn count shouldn't be higher than lowest conn count"
+		"   hg_count=%ld lo_count=%ld",
+		hg_count, lo_count
+	);
+}
+
+void update_conn_thread_map(vector<MYSQL*>& conns, map<string,vector<MYSQL*>>& m_thread_conns) {
+	for (MYSQL* myconn : conns) {
+		json j_session = fetch_internal_session(myconn, false);
+		string thread_addr { j_session["thread"] };
+
+		m_thread_conns[thread_addr].push_back(myconn);
+	}
+}
+
+int main(int argc, char** argv) {
+	CommandLine cl;
+
+	if (cl.getEnv()) {
+		diag("Failed to get the required environmental variables.");
+		return EXIT_FAILURE;
+	}
+
+	plan(1);
+
+	auto start = std::chrono::system_clock::now();
+	std::chrono::duration<double> elapsed {};
+
+	incr_proc_limits(ITER_CONN_COUNT);
+
+	map<string,vector<MYSQL*>> m_thread_conns {};
+
+	while (elapsed.count() < TEST_DURATION_SEC) {
+		pair<uint32_t,vector<MYSQL*>> p_err_conns { create_frontend_conns(cl, ITER_CONN_COUNT) };
+		if (p_err_conns.first) {
+			diag("Frontend conn creation failed; aborting further testing   err=%d", p_err_conns.first);
+			return EXIT_FAILURE;
+		}
+
+		update_conn_thread_map(p_err_conns.second, m_thread_conns);
+
+		for (MYSQL* conn : p_err_conns.second) {
+			mysql_close(conn);
+		}
+
+		auto it_end = std::chrono::system_clock::now();
+		elapsed = it_end - start;
+	}
+
+	check_thread_conn_dist(m_thread_conns);
+}