diff --git a/.github/workflows/ci-basictests.yml b/.github/workflows/ci-basictests.yml new file mode 100644 index 0000000000..3dee5c7ce0 --- /dev/null +++ b/.github/workflows/ci-basictests.yml @@ -0,0 +1,201 @@ + +name: CI-basictests + +on: + push: + branches: [ "v2.x" ] + paths-ignore: + - '.github/**' + - '**.md' + pull_request: + branches: [ "v2.x" ] + paths-ignore: + - '.github/**' + - '**.md' +# schedule: +# - cron: '15 13 * * 3' + workflow_dispatch: + +jobs: + build-n-test: + runs-on: ubuntu-22.04 + steps: + + - name: Install dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y python3-pymysql python3-structlog sysbench mycli + sudo pip3 install fastcov + + wget https://github.com/openark/orchestrator/releases/download/v3.2.6/orchestrator-client_3.2.6_amd64.deb + sudo dpkg -i orchestrator-client_3.2.6_amd64.deb + + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29 + wget https://repo.mysql.com/mysql-apt-config_0.8.24-1_all.deb + sudo dpkg -i mysql-apt-config_0.8.24-1_all.deb + sudo apt-get update -y + sudo apt-cache policy mysql-shell + sudo apt-get install -y mysql-shell + + sudo sed -i 's/8.0/5.7/' /etc/apt/sources.list.d/mysql.list + sudo sed -i 's/jammy/bionic/' /etc/apt/sources.list.d/mysql.list + sudo apt-get update -y + sudo apt-cache policy libmysqlclient-dev + sudo apt-get install -y --allow-downgrades libmysqlclient-dev=5.7* + + - name: Checkout proxysql + uses: actions/checkout@v3 + with: + repository: 'sysown/proxysql' +# ref: 'v2.x' + fetch-depth: 0 + path: 'proxysql' + + - name: Set GIT_VERSION + run: | + cd proxysql/ + git fetch --tags --force + echo "GIT_VERSION=$(git describe --long --abbrev=7)" >> $GITHUB_ENV + + - name: Fix paths + run: | + sudo ln -s ${{ github.workspace }}/proxysql /opt/proxysql + + - name: Checkout jenkins_build_scripts + uses: actions/checkout@v3 + with: + repository: 'proxysql/jenkins-build-scripts' + ref: 'kubernetes' + fetch-depth: 0 + path: 'jenkins-build-scripts' + token: ${{ secrets.GH_TOKEN }} + submodules: 'false' + + - name: Configure env.sh + run: | + cd jenkins-build-scripts + #git rm --cached proxysql_qa_test_suite + + # configure paths + sed -i "s|JENKINS_SCRIPTS_PATH=.*|JENKINS_SCRIPTS_PATH=${{ github.workspace }}/jenkins-build-scripts|" env.sh + sed -i "s|WORKSPACE=.*|WORKSPACE=${{ github.workspace }}/proxysql|" env.sh + + # select tests + sed -i "s|TEST_PY_INTERNAL=.*|TEST_PY_INTERNAL=0|" env.sh + sed -i "s|TEST_PY_BENCHMARK=.*|TEST_PY_BENCHMARK=1|" env.sh + sed -i "s|TEST_PY_CHUSER=.*|TEST_PY_CHUSER=1|" env.sh + sed -i "s|TEST_PY_STATS=.*|TEST_PY_STATS=0|" env.sh + sed -i "s|TEST_PY_TAP=.*|TEST_PY_TAP=0|" env.sh + sed -i "s|TEST_PY_TAPINT=.*|TEST_PY_TAPINT=0|" env.sh + sed -i "s|TEST_PY_FAILOVER=.*|TEST_PY_FAILOVER=1|" env.sh + #sed -i "s/TEST_PY_TAP_INCL=.*/TEST_PY_TAP_INCL=\"${{ matrix.testgroup }}\"/" env.sh + #sed -i "s|TEST_PY_TAP_EXCL=.*|TEST_PY_TAP_EXCL=\"\"|" env.sh + + - name: Patch TAP-tests + run: | + # apply patches + for PATCH in $(cd jenkins-build-scripts/test-scripts/patches; find . -type f); do + if [[ $PATCH =~ \.patch ]]; then + patch proxysql/test/tap/${PATCH%.patch} jenkins-build-scripts/test-scripts/patches/${PATCH} + elif [[ ! -f jenkins-build-scripts/test-scripts/patches/${PATCH#./}.patch ]]; then + cp jenkins-build-scripts/test-scripts/patches/${PATCH#./} proxysql/test/tap/${PATCH#./} + fi + done + + - name: Build + run: | + cd proxysql/ + # fix compile issues + grep -rl "" test/ | xargs sed -i 's||"curl/curl.h"|' + sed -i 's|-I/usr/include/mysql |-I/usr/include/mysql -I$(CURL_IDIR) |' test/tap/tests/Makefile + + # build proxysql + sed -i "/command/c \ command: bash -l -c 'cd /opt/proxysql && make debug_clickhouse WITHGCOV=1'" docker-compose.yml + #cat docker-compose.yml + make ubuntu22-dbg + + # build tap tests + sed -i "/command/c \ command: bash -l -c 'cd /opt/proxysql && make build_tap_test_debug WITHGCOV=1'" docker-compose.yml + #cat docker-compose.yml + #make ubuntu22-dbg + + # remove irrelevant tests (failing) + cd test/tap/tests + rm -f *clickhouse*-t + #rm -f *cluster*-t + rm -f *binlog*-t + rm -f *mariadb*-t + rm -f reg_test_3992_fast_forward_malformed_packet*-t + #rm -f reg_test_3847_admin_lock-t + + #rm -f set_testing-240-t + #rm -f test_com_reset_connection_com_change_user*-t + #rm -f max_connections_ff-t + #rm -f test_server_sess_status-t + + # remove long running tests (passing) + #rm -f multiple_prepared_statements-t + #rm -f mysql-mirror1-t + + - name: Run proxysql + run: | + set -x + set +e + cd ${{ github.workspace }}/jenkins-build-scripts + source ./env.sh + ./cluster_start.bash + sleep 10 + + cd ${{ github.workspace }}/proxysql + mkdir -p ci_infra_logs/regular_infra/proxysql + cd src + mkdir coverage_reports + + (./proxysql --clickhouse-server --sqlite3-server --idle-threads -f -c "$DOCKER_SCRIPT_PATH/conf/proxysql/proxysql.cnf" -D $REGULAR_INFRA_DATADIR &> $REGULAR_INFRA_DATADIR/proxysql.log) & + sleep 10 + mysql -uadmin -padmin -h127.0.0.1 -P6032 -e "SELECT version();" + + cd ${{ github.workspace }}/jenkins-build-scripts + ./cluster_init.bash + sleep 10 + + - name: Run infra + run: | + cd ${{ github.workspace }}/jenkins-build-scripts + source ./env.sh + + cd ${{ github.workspace }}/jenkins-build-scripts/infra-docker-hoster + #docker-compose up -d + ./docker-compose-init.bash + + cd ${{ github.workspace }}/jenkins-build-scripts/infra-mysql57 + sed -i "s/\${INFRA}-\${CONTAINER}-1/\${INFRA}_\${CONTAINER}_1/" docker-compose-init.bash + ./docker-compose-init.bash + + - name: Basic tests + run: | + set +e + cd ${{ github.workspace }}/jenkins-build-scripts + + source ./env.sh + env | sort + sudo -E ./test-scripts/bin/proxysql-tester.py + RC=$? + + exit $RC + + - name: Fix premissions + if: always() + run: | + sudo chmod -R 777 ${{ github.workspace }}/* + + - name: Archive artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: ci-selftests-${{ env.GIT_VERSION }}-run#${{ github.run_number }} + path: | + proxysql/ci_gcov_logs/ + proxysql/ci_infra_logs/ + proxysql/ci_tests_logs/ + diff --git a/.github/workflows/ci-repltests.yml b/.github/workflows/ci-repltests.yml new file mode 100644 index 0000000000..ccab745fa4 --- /dev/null +++ b/.github/workflows/ci-repltests.yml @@ -0,0 +1,88 @@ +name: CI-repltests + +on: + push: + branches: [ "v2.x" ] + paths-ignore: + - '.github/**' + - '**.md' + pull_request: + branches: [ "v2.x" ] + paths-ignore: + - '.github/**' + - '**.md' +# schedule: +# - cron: '15 13 * * 3' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + + - name: Install dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y sysbench gettext + + - name: Checkout proxysql + uses: actions/checkout@v3 + with: + repository: 'sysown/proxysql' +# ref: 'v2.x' + fetch-depth: 0 + path: 'proxysql' + + - name: Checkout jenkins_build_scripts + uses: actions/checkout@v3 + with: + repository: 'proxysql/jenkins-build-scripts' + ref: 'proxysql_repl_tests' + fetch-depth: 0 + path: 'jenkins-build-scripts' + token: ${{ secrets.GH_TOKEN }} + + - name: Set GIT_VERSION + run: | + cd proxysql/ + git fetch --tags --force + echo "GIT_VERSION=$(git describe --long --abbrev=7)" >> $GITHUB_ENV + - name: Build + run: | + cd proxysql/ + make ubuntu22-dbg + + - name: Docker-hoster + run: | + cd jenkins-build-scripts/infra-docker-hoster + docker-compose up -d + + - name: Replication-tests + run: | + set +e + + cd jenkins-build-scripts + sed -i "s|#!/usr/bin/bash|#!/usr/bin/bash +e|" proxysql_repl_tests/bin/debezium-check.bash + sed -i "s|#!/usr/bin/bash|#!/usr/bin/bash +e|" proxysql_repl_tests/exec_repl_test.sh + sed -i "s|JENKINS_SCRIPTS_PATH=.*|JENKINS_SCRIPTS_PATH=${{ github.workspace }}/jenkins-build-scripts|" env.sh + sed -i "s|WORKSPACE=.*|WORKSPACE=${{ github.workspace }}/proxysql|" env.sh + + cd proxysql_repl_tests + #./exec_repl_test.sh 5.7 no-ssl debezium + ./exec_all_repl_tests.sh + RC=$? + + #./docker-compose-destroy.bash + sudo chmod -R 777 ${{ github.workspace }}/* + exit $RC + + - name: Archive artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: ci-selftests-${{ env.GIT_VERSION }}-run#${{ github.run_number }} + path: | + proxysql/src/ + proxysql/ci_infra_logs/ + + diff --git a/.github/workflows/package-build.yml b/.github/workflows/package-build.yml index 726b538823..40b75ababf 100644 --- a/.github/workflows/package-build.yml +++ b/.github/workflows/package-build.yml @@ -107,16 +107,16 @@ jobs: echo "BIN_PKG=$(ls -1 binaries/*[mb])" >> $GITHUB_ENV echo "BIN_HASH=$(ls -1 binaries/*.id-hash)" >> $GITHUB_ENV -# - name: Deploy to Repo -# uses: easingthemes/ssh-deploy@main -# env: -# SSH_PRIVATE_KEY: ${{ secrets.REPO_PRIVATE_KEY }} -# ARGS: "-aic" -# SOURCE: ${{ env.BIN_PKG }} -# REMOTE_HOST: ${{ secrets.REPO_HOST }} -# REMOTE_USER: ${{ secrets.REPO_USER }} -# TARGET: ${{ secrets.REPO_TARGET }}/binaries-${{ env.GIT_VERSION }}/ -# EXCLUDE: binaries/.gitignore + - name: Deploy to Repo + uses: easingthemes/ssh-deploy@main + env: + SSH_PRIVATE_KEY: ${{ secrets.REPO_PRIVATE_KEY }} + ARGS: "-aic" + SOURCE: ${{ env.BIN_PKG }} + REMOTE_HOST: ${{ secrets.REPO_HOST }} + REMOTE_USER: ${{ secrets.REPO_USER }} + TARGET: ${{ secrets.REPO_TARGET }}/binaries-${{ env.GIT_VERSION }}/ + EXCLUDE: binaries/.gitignore - name: Push packages env: diff --git a/Makefile b/Makefile index 5920182dfc..be8f0afe7d 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,9 @@ testgrouprep: build_deps_debug build_lib_testgrouprep build_src_testgrouprep .PHONY: testreadonly testreadonly: build_deps_debug build_lib_testreadonly build_src_testreadonly +.PHONY: testreplicationlag +testreplicationlag: build_deps_debug build_lib_testreplicationlag build_src_testreplicationlag + .PHONY: testall testall: build_deps_debug build_lib_testall build_src_testall @@ -151,13 +154,21 @@ build_src_testreadonly: build_deps build_lib_testreadonly build_lib_testreadonly: build_deps_debug cd lib && OPTZ="${O0} -ggdb -DDEBUG -DTEST_READONLY" CC=${CC} CXX=${CXX} ${MAKE} +.PHONY: build_src_testreplicationlag +build_src_testreplicationlag: build_deps build_lib_testreplicationlag + cd src && OPTZ="${O0} -ggdb -DDEBUG -DTEST_REPLICATIONLAG" CC=${CC} CXX=${CXX} ${MAKE} + +.PHONY: build_lib_testreplicationlag +build_lib_testreplicationlag: build_deps_debug + cd lib && OPTZ="${O0} -ggdb -DDEBUG -DTEST_REPLICATIONLAG" CC=${CC} CXX=${CXX} ${MAKE} + .PHONY: build_src_testall build_src_testall: build_deps build_lib_testall - cd src && OPTZ="${O0} -ggdb -DDEBUG -DTEST_AURORA -DTEST_GALERA -DTEST_GROUPREP -DTEST_READONLY" CC=${CC} CXX=${CXX} ${MAKE} + cd src && OPTZ="${O0} -ggdb -DDEBUG -DTEST_AURORA -DTEST_GALERA -DTEST_GROUPREP -DTEST_READONLY -DTEST_REPLICATIONLAG" CC=${CC} CXX=${CXX} ${MAKE} .PHONY: build_lib_testall build_lib_testall: build_deps_debug - cd lib && OPTZ="${O0} -ggdb -DDEBUG -DTEST_AURORA -DTEST_GALERA -DTEST_GROUPREP -DTEST_READONLY" CC=${CC} CXX=${CXX} ${MAKE} + cd lib && OPTZ="${O0} -ggdb -DDEBUG -DTEST_AURORA -DTEST_GALERA -DTEST_GROUPREP -DTEST_READONLY -DTEST_REPLICATIONLAG" CC=${CC} CXX=${CXX} ${MAKE} .PHONY: build_tap_test build_tap_test: build_src diff --git a/README.md b/README.md index 07adb267e3..7a5236f204 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![CI-selftests](https://github.com/sysown/proxysql/actions/workflows/ci-selftests.yml/badge.svg)](https://github.com/sysown/proxysql/actions/workflows/ci-selftests.yml) +[![CI-repltests](https://github.com/sysown/proxysql/actions/workflows/ci-repltests.yml/badge.svg)](https://github.com/sysown/proxysql/actions/workflows/ci-repltests.yml) [![CodeQL](https://github.com/sysown/proxysql/actions/workflows/codeql.yml/badge.svg)](https://github.com/sysown/proxysql/actions/workflows/codeql.yml) [![Package-Build](https://github.com/sysown/proxysql/actions/workflows/package-build.yml/badge.svg)](https://github.com/sysown/proxysql/actions/workflows/package-build.yml) diff --git a/deps/Makefile b/deps/Makefile index 47c0f3c3b2..aac8709cca 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -82,6 +82,11 @@ endif libinjection: libinjection/libinjection/src/libinjection.a +BIO_MATCH := $(shell ./libssl/verify-bio_st-match.sh \&>/dev/null; echo $$?) +ifneq ($(BIO_MATCH),0) +$(error Incompatible OpenSSL version: struct bio_st mismatch!) +endif + libssl/openssl/libssl.a: cd libssl && rm -rf openssl-openssl-*/ openssl-3*/ || true cd libssl && tar -zxf openssl-*.tar.gz @@ -93,6 +98,7 @@ libssl/openssl/libssl.a: libssl: libssl/openssl/libssl.a + MIN_VERSION := 4.9.0 GCC_VERSION := $(shell gcc -dumpversion) SORTED_VERSIONS := $(shell echo -e "$(GCC_VERSION)\n$(MIN_VERSION)" | sort -V) diff --git a/deps/libssl/README.md b/deps/libssl/README.md index 5181cbd5eb..eab127418f 100644 --- a/deps/libssl/README.md +++ b/deps/libssl/README.md @@ -6,6 +6,13 @@ In ProxySQL 2.1.1 , libssl was upgraded to version 1.1.1j In ProxySQL 2.4.0 , libssl was upgraded from version 1.1.1j to 3.0.2 +In ProxySQL 2.4.8 , libssl was upgraded from version 3.0.2 to 3.0.8 + +In ProxySQL 2.5.x , libssl was upgraded from version 3.0.8 to 3.1.0 + + Do not upgrade without extensive testing. See note about `struct bio_st` in MySQL_Data_Stream.cpp . + +Run `verify-bio_st-match.sh` to confirm compatibility. diff --git a/deps/libssl/openssl b/deps/libssl/openssl index 75c1d8f45b..9763886482 120000 --- a/deps/libssl/openssl +++ b/deps/libssl/openssl @@ -1 +1 @@ -openssl-3.0.8 \ No newline at end of file +openssl-3.1.0 \ No newline at end of file diff --git a/deps/libssl/openssl-3.0.8.tar.gz b/deps/libssl/openssl-3.1.0.tar.gz similarity index 62% rename from deps/libssl/openssl-3.0.8.tar.gz rename to deps/libssl/openssl-3.1.0.tar.gz index 6a01626a84..0aa5c9e2f8 100644 Binary files a/deps/libssl/openssl-3.0.8.tar.gz and b/deps/libssl/openssl-3.1.0.tar.gz differ diff --git a/deps/libssl/verify-bio_st-match.sh b/deps/libssl/verify-bio_st-match.sh index 93b35e3c75..b4903e032a 100755 --- a/deps/libssl/verify-bio_st-match.sh +++ b/deps/libssl/verify-bio_st-match.sh @@ -4,16 +4,16 @@ pushd $(dirname $0) &>/dev/null trap 'popd &>/dev/null' EXIT -echo "checking for './deps/libssl/openssl/crypto/bio/bio_local.h'" +echo "checking for 'deps/libssl/openssl/crypto/bio/bio_local.h'" if [[ ! -e './openssl/crypto/bio/bio_local.h' ]]; then echo "extracting 'openssl-*.tar.gz'" tar -zxf openssl-*.tar.gz fi -echo "extracting 'struct bio_st' from './deps/libssl/openssl/crypto/bio/bio_local.h'" +echo "extracting 'struct bio_st' from 'deps/libssl/openssl/crypto/bio/bio_local.h'" DEPBIOST=$(cd ../../; cat ./deps/libssl/openssl/crypto/bio/bio_local.h | sed -n '/^struct bio_st {/,/}/p') -echo "extracting 'struct bio_st' from './lib/mysql_data_stream.cpp'" +echo "extracting 'struct bio_st' from 'lib/mysql_data_stream.cpp'" LIBBIOST=$(cd ../../; cat ./lib/mysql_data_stream.cpp | sed '/^\/\*/,/*\//d' | sed -n '/^struct bio_st {/,/}/p') echo -n "Comparing ... " diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index 45ca581334..20cc69d5af 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -379,21 +379,32 @@ struct hg_metrics_map_idx { }; /** - * @brief Required server info for the read_only Monitoring actions. + * @brief Required server info for the read_only Monitoring actions and replication_lag Monitoring actions. */ +using hostgroupid_t = int; using hostname_t = std::string; -using port_t = int; +using address_t = std::string; +using port_t = unsigned int; using read_only_t = int; +using current_replication_lag = int; using read_only_server_t = std::tuple; +using replication_lag_server_t = std::tuple; enum READ_ONLY_SERVER_T { - HOSTNAME = 0, - PORT, - READONLY, - __SIZE + ROS_HOSTNAME = 0, + ROS_PORT, + ROS_READONLY, + ROS__SIZE +}; + +enum REPLICATION_LAG_SERVER_T { + RLS_HOSTGROUP_ID = 0, + RLS_ADDRESS, + RLS_PORT, + RLS_CURRENT_REPLICATION_LAG, + RLS__SIZE }; -// class MySQL_HostGroups_Manager { private: @@ -762,8 +773,8 @@ class MySQL_HostGroups_Manager { void push_MyConn_to_pool_array(MySQL_Connection **, unsigned int); void destroy_MyConn_from_pool(MySQL_Connection *, bool _lock=true); - void replication_lag_action_inner(MyHGC *, char*, unsigned int, int); - void replication_lag_action(int, char*, unsigned int, int); + void replication_lag_action_inner(MyHGC *, const char*, unsigned int, int); + void replication_lag_action(const std::list& mysql_servers); void read_only_action(char *hostname, int port, int read_only); void read_only_action_v2(const std::list& mysql_servers); unsigned int get_servers_table_version(); diff --git a/include/ProxySQL_Cluster.hpp b/include/ProxySQL_Cluster.hpp index 7be5700503..0dd72a3ede 100644 --- a/include/ProxySQL_Cluster.hpp +++ b/include/ProxySQL_Cluster.hpp @@ -274,6 +274,7 @@ class ProxySQL_Cluster_Nodes { bool Update_Node_Metrics(char * _h, uint16_t _p, MYSQL_RES *_r, unsigned long long _response_time); bool Update_Global_Checksum(char * _h, uint16_t _p, MYSQL_RES *_r); bool Update_Node_Checksums(char * _h, uint16_t _p, MYSQL_RES *_r); + void Reset_Global_Checksums(bool lock); void update_prometheus_nodes_metrics(); SQLite3_result * dump_table_proxysql_servers(); SQLite3_result * stats_proxysql_servers_checksums(); @@ -453,6 +454,9 @@ class ProxySQL_Cluster { bool Update_Node_Checksums(char * _h, uint16_t _p, MYSQL_RES *_r = NULL) { return nodes.Update_Node_Checksums(_h, _p, _r); } + void Reset_Global_Checksums(bool lock) { + nodes.Reset_Global_Checksums(lock); + } SQLite3_result *dump_table_proxysql_servers() { return nodes.dump_table_proxysql_servers(); } diff --git a/include/SQLite3_Server.h b/include/SQLite3_Server.h index ae389ad417..ce33a6b6ec 100644 --- a/include/SQLite3_Server.h +++ b/include/SQLite3_Server.h @@ -54,7 +54,11 @@ class SQLite3_Server { std::unordered_map readonly_map; std::vector *tables_defs_readonly; #endif // TEST_READONLY -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#ifdef TEST_REPLICATIONLAG + std::unordered_map replicationlag_map; + std::vector* tables_defs_replicationlag; +#endif // TEST_REPLICATIONLAG +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) void insert_into_tables_defs(std::vector *, const char *table_name, const char *table_def); void drop_tables_defs(std::vector *tables_defs); void check_and_build_standard_tables(SQLite3DB *db, std::vector *tables_defs); @@ -92,6 +96,14 @@ class SQLite3_Server { return readonly_map.size(); } #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG + pthread_mutex_t test_replicationlag_mutex; + void load_replicationlag_table(MySQL_Session* sess); + int replicationlag_test_value(const char* p); + int replicationlag_map_size() { + return replicationlag_map.size(); + } +#endif // TEST_REPLICATIONLAG SQLite3_Server(); ~SQLite3_Server(); char **get_variables_list(); diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h index 6d1ddf5643..b5d1d3c675 100644 --- a/include/proxysql_admin.h +++ b/include/proxysql_admin.h @@ -5,6 +5,7 @@ #include #include +#include "query_processor.h" #include "proxy_defines.h" #include "proxysql.h" #include "cpp.h" @@ -275,8 +276,8 @@ class ProxySQL_Admin { void flush_mysql_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum = "", const time_t epoch = 0); char **get_variables_list(); - bool set_variable(char *name, char *value); - void flush_admin_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum = "", const time_t epoch = 0); + bool set_variable(char *name, char *value, bool lock = true); + void flush_admin_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum = "", const time_t epoch = 0, bool lock = true); void flush_admin_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime=false); void disk_upgrade_mysql_query_rules(); void disk_upgrade_mysql_servers(); @@ -337,6 +338,10 @@ class ProxySQL_Admin { SQLite3DB *configdb; // on disk SQLite3DB *monitordb; // in memory SQLite3DB *statsdb_disk; // on disk +#ifdef DEBUG + SQLite3DB *debugdb_disk; // on disk for debug + int debug_output; +#endif int pipefd[2]; void print_version(); bool init(); @@ -436,7 +441,7 @@ class ProxySQL_Admin { void load_scheduler_to_runtime(); void save_scheduler_runtime_to_database(bool); - void load_admin_variables_to_runtime(const std::string& checksum = "", const time_t epoch = 0) { flush_admin_variables___database_to_runtime(admindb, true, checksum, epoch); } + void load_admin_variables_to_runtime(const std::string& checksum = "", const time_t epoch = 0, bool lock = true) { flush_admin_variables___database_to_runtime(admindb, true, checksum, epoch, lock); } void save_admin_variables_from_runtime() { flush_admin_variables___runtime_to_database(admindb, true, true, false); } void load_or_update_global_settings(SQLite3DB *); @@ -446,7 +451,12 @@ class ProxySQL_Admin { void p_update_metrics(); void stats___mysql_query_rules(); - void stats___mysql_query_digests(bool reset, bool copy=false); + int stats___save_mysql_query_digest_to_sqlite( + const bool reset, const bool copy, const SQLite3_result *resultset, + const umap_query_digest *digest_umap, const umap_query_digest_text *digest_text_umap + ); + int stats___mysql_query_digests(bool reset, bool copy=false); + int stats___mysql_query_digests_v2(bool reset, bool copy, bool use_resultset); //void stats___mysql_query_digests_reset(); void stats___mysql_commands_counters(); void stats___mysql_processlist(); @@ -538,6 +548,10 @@ class ProxySQL_Admin { void enable_readonly_testing(); #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG + void enable_replicationlag_testing(); +#endif // TEST_REPLICATIONLAG + unsigned int ProxySQL_Test___GenerateRandom_mysql_query_rules_fast_routing(unsigned int, bool); bool ProxySQL_Test___Verify_mysql_query_rules_fast_routing(int *ret1, int *ret2, int cnt, int dual); void ProxySQL_Test___MySQL_HostGroups_Manager_generate_many_clusters(); diff --git a/include/proxysql_debug.h b/include/proxysql_debug.h index c34d1897b1..bf9fa013c7 100644 --- a/include/proxysql_debug.h +++ b/include/proxysql_debug.h @@ -211,4 +211,12 @@ SQLite3_result* proxysql_get_message_stats(bool reset=false); */ void proxysql_init_debug_prometheus_metrics(); + +/** + * @brief Set or unset if Admin has debugdb_disk fully initialized + */ +void proxysql_set_admin_debugdb_disk(SQLite3DB *_db); + +void proxysql_set_admin_debug_output(unsigned int _do); + #endif diff --git a/include/proxysql_glovars.hpp b/include/proxysql_glovars.hpp index 4d7d0529df..28778c45b8 100644 --- a/include/proxysql_glovars.hpp +++ b/include/proxysql_glovars.hpp @@ -125,18 +125,29 @@ class ProxySQL_GlobalVariables { } statuses; pthread_mutex_t checksum_mutex; time_t epoch_version; - struct { - ProxySQL_Checksum_Value admin_variables; - ProxySQL_Checksum_Value mysql_query_rules; - ProxySQL_Checksum_Value mysql_servers; - ProxySQL_Checksum_Value mysql_users; - ProxySQL_Checksum_Value mysql_variables; - ProxySQL_Checksum_Value ldap_variables; - ProxySQL_Checksum_Value proxysql_servers; - uint64_t global_checksum; - unsigned long long updates_cnt; - unsigned long long dumped_at; - } checksums_values; + /** + * @brief Anonymous union holding just the member 'checksums_values'. + * @details This encapsulation is performed to prevent the call to 'ProxySQL_Checksum_Value' destructor for + * 'checksums_values' members. These checksums memory is never freed during ProxySQL execution + * lifetime (only reused during update operations), and they are concurrently access by multiple threads, + * including during shutdown phase. Since 'GloVars' (unique instance of this class) is declared global, + * it's impossible to control de destruction order with respect to the other modules. To avoid invalid + * memory accesses during shutdown, we avoid calling the destructor of the members at all. + */ + union { + struct { + ProxySQL_Checksum_Value admin_variables; + ProxySQL_Checksum_Value mysql_query_rules; + ProxySQL_Checksum_Value mysql_servers; + ProxySQL_Checksum_Value mysql_users; + ProxySQL_Checksum_Value mysql_variables; + ProxySQL_Checksum_Value ldap_variables; + ProxySQL_Checksum_Value proxysql_servers; + uint64_t global_checksum; + unsigned long long updates_cnt; + unsigned long long dumped_at; + } checksums_values; + }; uint64_t generate_global_checksum(); ProxySQL_GlobalVariables(); ~ProxySQL_GlobalVariables(); diff --git a/include/query_processor.h b/include/query_processor.h index 15f4ceeca8..5e17903c03 100644 --- a/include/query_processor.h +++ b/include/query_processor.h @@ -61,8 +61,12 @@ class QP_query_digest_stats { unsigned long long rows_sent; int hid; QP_query_digest_stats(char *u, char *s, uint64_t d, char *dt, int h, char *ca); - void add_time(unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs); + void add_time( + unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs, + unsigned long long cnt = 1 + ); ~QP_query_digest_stats(); + char *get_digest_text(const umap_query_digest_text *digest_text_umap); char **get_row(umap_query_digest_text *digest_text_umap, query_digest_stats_pointers_t *qdsp); }; @@ -319,6 +323,10 @@ class Query_Processor { SQLite3_result * get_stats_commands_counters(); SQLite3_result * get_query_digests(); SQLite3_result * get_query_digests_reset(); + std::pair get_query_digests_v2(const bool use_resultset = true); + std::pair get_query_digests_reset_v2( + const bool copy, const bool use_resultset = true + ); void get_query_digests_reset(umap_query_digest *uqd, umap_query_digest_text *uqdt); unsigned long long purge_query_digests(bool async_purge, bool parallel, char **msg); unsigned long long purge_query_digests_async(char **msg); diff --git a/include/sqlite3db.h b/include/sqlite3db.h index 36e22149f0..b4d3c52f3b 100644 --- a/include/sqlite3db.h +++ b/include/sqlite3db.h @@ -7,6 +7,16 @@ #include #define PROXYSQL_SQLITE3DB_PTHREAD_MUTEX +#ifndef SAFE_SQLITE3_STEP2 +#define SAFE_SQLITE3_STEP2(_stmt) do {\ + do {\ + rc=(*proxy_sqlite3_step)(_stmt);\ + if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ + usleep(100);\ + }\ + } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ +} while (0) +#endif // SAFE_SQLITE3_STEP2 #ifndef MAIN_PROXY_SQLITE3 extern int (*proxy_sqlite3_bind_double)(sqlite3_stmt*, int, double); diff --git a/lib/ClickHouse_Server.cpp b/lib/ClickHouse_Server.cpp index ca71746cf3..fecfab268f 100644 --- a/lib/ClickHouse_Server.cpp +++ b/lib/ClickHouse_Server.cpp @@ -58,15 +58,6 @@ } while (rc!=SQLITE_DONE);\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) - #include "clickhouse/client.h" using namespace clickhouse; diff --git a/lib/MySQL_HostGroups_Manager.cpp b/lib/MySQL_HostGroups_Manager.cpp index 1516f17a9a..b6d1ffefa6 100644 --- a/lib/MySQL_HostGroups_Manager.cpp +++ b/lib/MySQL_HostGroups_Manager.cpp @@ -42,15 +42,6 @@ static unsigned long long array_mysrvc_cands = 0; } while (rc!=SQLITE_DONE);\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) - extern ProxySQL_Admin *GloAdmin; extern MySQL_Threads_Handler *GloMTH; @@ -1868,8 +1859,7 @@ bool MySQL_HostGroups_Manager::commit( generate_mysql_hostgroup_attributes_table(); } - - //if (GloAdmin && GloAdmin->checksum_variables.checksum_mysql_servers) + // Checksums are always generated - 'admin-checksum_*' deprecated { uint64_t hash1 = 0, hash2 = 0; SpookyHash myhash; @@ -3439,7 +3429,7 @@ void MySQL_HostGroups_Manager::add(MySrvC *mysrvc, unsigned int _hid) { myhgc->mysrvs->add(mysrvc); } -void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, char *address, unsigned int port, int current_replication_lag) { +void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, const char *address, unsigned int port, int current_replication_lag) { int j; for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); @@ -3491,23 +3481,42 @@ void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, char * } } -void MySQL_HostGroups_Manager::replication_lag_action(int _hid, char *address, unsigned int port, int current_replication_lag) { - GloAdmin->mysql_servers_wrlock(); +void MySQL_HostGroups_Manager::replication_lag_action(const std::list& mysql_servers) { + + //this method does not use admin table, so this lock is not needed. + //GloAdmin->mysql_servers_wrlock(); + unsigned long long curtime1 = monotonic_time(); wrlock(); - if (mysql_thread___monitor_replication_lag_group_by_host == false) { - // legacy check. 1 check per server per hostgroup - MyHGC *myhgc = MyHGC_find(_hid); - replication_lag_action_inner(myhgc,address,port,current_replication_lag); - } else { - // only 1 check per server, no matter the hostgroup - // all hostgroups must be searched - for (unsigned int i=0; ilen; i++) { - MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); - replication_lag_action_inner(myhgc,address,port,current_replication_lag); + + for (const auto& server : mysql_servers) { + + const int hid = std::get(server); + const std::string& address = std::get(server); + const unsigned int port = std::get(server); + const int current_replication_lag = std::get(server); + + if (mysql_thread___monitor_replication_lag_group_by_host == false) { + // legacy check. 1 check per server per hostgroup + MyHGC *myhgc = MyHGC_find(hid); + replication_lag_action_inner(myhgc,address.c_str(),port,current_replication_lag); + } + else { + // only 1 check per server, no matter the hostgroup + // all hostgroups must be searched + for (unsigned int i=0; ilen; i++) { + MyHGC*myhgc=(MyHGC*)MyHostGroups->index(i); + replication_lag_action_inner(myhgc,address.c_str(),port,current_replication_lag); + } } } + wrunlock(); - GloAdmin->mysql_servers_wrunlock(); + //GloAdmin->mysql_servers_wrunlock(); + + unsigned long long curtime2 = monotonic_time(); + curtime1 = curtime1 / 1000; + curtime2 = curtime2 / 1000; + proxy_debug(PROXY_DEBUG_MONITOR, 7, "MySQL_HostGroups_Manager::replication_lag_action() locked for %llums (server count:%ld)\n", curtime2 - curtime1, mysql_servers.size()); } /** @@ -4592,9 +4601,9 @@ void MySQL_HostGroups_Manager::read_only_action_v2(const std::list(server); - const int port = std::get(server); - const int read_only = std::get(server); + const std::string& hostname = std::get(server); + const int port = std::get(server); + const int read_only = std::get(server); const std::string& srv_id = hostname + ":::" + std::to_string(port); auto itr = hostgroup_server_mapping.find(srv_id); diff --git a/lib/MySQL_Monitor.cpp b/lib/MySQL_Monitor.cpp index e14a3eb300..e6d9e0943b 100644 --- a/lib/MySQL_Monitor.cpp +++ b/lib/MySQL_Monitor.cpp @@ -50,15 +50,6 @@ static MySQL_Monitor *GloMyMon; } while (rc!=SQLITE_DONE);\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) - using std::string; using std::set; using std::vector; @@ -632,6 +623,10 @@ void MySQL_Monitor_State_Data::init_async() { break; case MON_REPLICATION_LAG: async_state_machine_ = ASYNC_QUERY_START; +#ifdef TEST_REPLICATIONLAG + query_ = "SELECT SLAVE STATUS "; // replaced SHOW with SELECT to avoid breaking simulator logic + query_ += std::string(hostname) + ":" + std::to_string(port); +#else if (mysql_thread___monitor_replication_lag_use_percona_heartbeat && mysql_thread___monitor_replication_lag_use_percona_heartbeat[0] != '\0') { use_percona_heartbeat = true; @@ -640,6 +635,7 @@ void MySQL_Monitor_State_Data::init_async() { } else { query_ = "SHOW SLAVE STATUS"; } +#endif task_timeout_ = mysql_thread___monitor_replication_lag_timeout; task_handler_ = &MySQL_Monitor_State_Data::replication_lag_handler; break; @@ -2617,6 +2613,14 @@ void * monitor_replication_lag_thread(void *arg) { mmsd->t1=monotonic_time(); mmsd->interr=0; // reset the value + +#ifdef TEST_REPLICATIONLAG + { + std::string s = "SELECT SLAVE STATUS "; // replaced SHOW with SELECT to avoid breaking simulator logic + s += std::string(mmsd->hostname) + ":" + std::to_string(mmsd->port); + mmsd->async_exit_status = mysql_query_start(&mmsd->interr, mmsd->mysql, s.c_str()); + } +#else if (percona_heartbeat_table) { int l = strlen(percona_heartbeat_table); if (l) { @@ -2631,6 +2635,7 @@ void * monitor_replication_lag_thread(void *arg) { if (use_percona_heartbeat == false) { mmsd->async_exit_status=mysql_query_start(&mmsd->interr,mmsd->mysql,"SHOW SLAVE STATUS"); } +#endif // TEST_REPLICATIONLAG while (mmsd->async_exit_status) { mmsd->async_exit_status=wait_for_mysql(mmsd->mysql, mmsd->async_exit_status); #ifdef DEBUG @@ -2721,13 +2726,18 @@ void * monitor_replication_lag_thread(void *arg) { int j=-1; num_fields = mysql_num_fields(mmsd->result); fields = mysql_fetch_fields(mmsd->result); +#ifdef TEST_REPLICATIONLAG + if (fields && num_fields == 1 ) +#else if ( fields && ( ( num_fields == 1 && use_percona_heartbeat == true ) || ( num_fields > 30 && use_percona_heartbeat == false ) ) - ) { + ) +#endif + { for(k = 0; k < num_fields; k++) { if (fields[k].name) { if (strcmp("Seconds_Behind_Master", fields[k].name)==0) { @@ -2768,7 +2778,7 @@ void * monitor_replication_lag_thread(void *arg) { SAFE_SQLITE3_STEP2(statement); rc=(*proxy_sqlite3_clear_bindings)(statement); ASSERT_SQLITE_OK(rc, mmsd->mondb); rc=(*proxy_sqlite3_reset)(statement); ASSERT_SQLITE_OK(rc, mmsd->mondb); - MyHGM->replication_lag_action(mmsd->hostgroup_id, mmsd->hostname, mmsd->port, repl_lag); + MyHGM->replication_lag_action({ {mmsd->hostgroup_id, mmsd->hostname, mmsd->port, repl_lag} }); (*proxy_sqlite3_finalize)(statement); if (mmsd->mysql_error_msg == NULL) { replication_lag_success = true; @@ -3675,12 +3685,14 @@ void gr_handle_actions_over_unresp_srvs(const vector& hosts_defs, * before placing the connection back into the 'ConnectionPool', on failure, we discard the connection. * @param mmsd The mmsd wrapper holding all information for returning the connection. */ -void handle_mmsd_mysql_conn(MySQL_Monitor_State_Data* mmsd) { +void handle_mmsd_mysql_conn(MySQL_Monitor_State_Data* mmsd, bool unregister_conn_on_failure) { if (mmsd == nullptr) return; if (mmsd->mysql) { if (mmsd->interr || mmsd->mysql_error_msg) { - GloMyMon->My_Conn_Pool->conn_unregister(mmsd); + if (unregister_conn_on_failure) { + GloMyMon->My_Conn_Pool->conn_unregister(mmsd); + } mysql_close(mmsd->mysql); } else { if (mmsd->created_conn) { @@ -3695,7 +3707,9 @@ void handle_mmsd_mysql_conn(MySQL_Monitor_State_Data* mmsd) { MyHGM->p_update_mysql_error_counter( p_mysql_error_type::proxysql, mmsd->hostgroup_id, mmsd->hostname, mmsd->port, mysql_errno(mmsd->mysql) ); - GloMyMon->My_Conn_Pool->conn_unregister(mmsd); + if (unregister_conn_on_failure) { + GloMyMon->My_Conn_Pool->conn_unregister(mmsd); + } mysql_close(mmsd->mysql); } } else { @@ -3742,7 +3756,7 @@ void gr_report_fetching_errs(MySQL_Monitor_State_Data* mmsd) { * @param mmsd The server 'MySQL_Monitor_State_Data' after the fetching is completed. It should either * hold a valid 'MYSQL_RES' or an error. */ -void async_gr_mon_actions_handler(MySQL_Monitor_State_Data* mmsd) { +void async_gr_mon_actions_handler(MySQL_Monitor_State_Data* mmsd, bool unregister_conn_on_failure) { // We base 'start_time' on the conn init for 'MySQL_Monitor_State_Data'. If a conn creation was // required, we take into account this time into account, otherwise we asume that 'start_time=t1'. uint64_t start_time = 0; @@ -3765,7 +3779,7 @@ void async_gr_mon_actions_handler(MySQL_Monitor_State_Data* mmsd) { } // Handle 'mmsd' MySQL conn return to 'ConnectionPool' - handle_mmsd_mysql_conn(mmsd); + handle_mmsd_mysql_conn(mmsd, unregister_conn_on_failure); } /** @@ -3881,7 +3895,7 @@ void* monitor_GR_thread_HG(void *arg) { // Handle 'mmsds' that failed to optain conns for (const unique_ptr& mmsd : fail_mmsds) { - async_gr_mon_actions_handler(mmsd.get()); + async_gr_mon_actions_handler(mmsd.get(), true); } // Update 't1' for subsequent fetch operations and reset errors @@ -6785,6 +6799,10 @@ class Monitor_Poll { for (unsigned int i = 0; i < len_;) { if (mmsds_[i]->task_handler(fds_[i].revents, fds_[i].events) != MySQL_Monitor_State_Data_Task_Result::TASK_RESULT_PENDING) { +#ifdef DEBUG + if (mmsds_[i]->get_task_result() != MySQL_Monitor_State_Data_Task_Result::TASK_RESULT_SUCCESS) + GloMyMon->My_Conn_Pool->conn_unregister(mmsds_[i]); +#endif // DEBUG ready_tasks.push_back(mmsds_[i]); remove_index_fast(i); @@ -6946,9 +6964,9 @@ bool MySQL_Monitor::monitor_ping_process_ready_tasks(const std::vectort2 - mmsd->t1) / 1000, mmsd->hostname, mmsd->port, (mmsd->mysql_error_msg ? mmsd->mysql_error_msg : "")); #endif // DEBUG } -#ifdef DEBUG - My_Conn_Pool->conn_unregister(mmsd); -#endif // DEBUG +//#ifdef DEBUG +// My_Conn_Pool->conn_unregister(mmsd); +//#endif // DEBUG mysql_close(mmsd->mysql); mmsd->mysql = NULL; } @@ -7111,7 +7129,7 @@ MySQL_Monitor_State_Data_Task_Result MySQL_Monitor_State_Data::generic_handler(s bool MySQL_Monitor::monitor_read_only_process_ready_tasks(const std::vector& mmsds) { - std::list> mysql_servers; + std::list mysql_servers; for (auto& mmsd : mmsds) { @@ -7136,9 +7154,9 @@ bool MySQL_Monitor::monitor_read_only_process_ready_tasks(const std::vectormysql, mmsd->mysql->net.fd, (mmsd->mysql_error_msg ? mmsd->mysql_error_msg : "")); #endif } -#ifdef DEBUG - My_Conn_Pool->conn_unregister(mmsd); -#endif // DEBUG +//#ifdef DEBUG +// My_Conn_Pool->conn_unregister(mmsd); +//#endif // DEBUG mysql_close(mmsd->mysql); mmsd->mysql = NULL; } @@ -7319,9 +7337,9 @@ bool MySQL_Monitor::monitor_group_replication_process_ready_tasks(const std::vec proxy_error("Got error: mmsd %p , MYSQL %p , FD %d : %s\n", mmsd, mmsd->mysql, mmsd->mysql->net.fd, (mmsd->mysql_error_msg ? mmsd->mysql_error_msg : "")); #endif } -#ifdef DEBUG - My_Conn_Pool->conn_unregister(mmsd); -#endif // DEBUG +//#ifdef DEBUG +// My_Conn_Pool->conn_unregister(mmsd); +//#endif // DEBUG mysql_close(mmsd->mysql); mmsd->mysql = NULL; } @@ -7511,7 +7529,7 @@ bool MySQL_Monitor::monitor_group_replication_process_ready_tasks_2( for (MySQL_Monitor_State_Data* mmsd : mmsds) { const MySQL_Monitor_State_Data_Task_Result task_result = mmsd->get_task_result(); assert(task_result != MySQL_Monitor_State_Data_Task_Result::TASK_RESULT_PENDING); - async_gr_mon_actions_handler(mmsd); + async_gr_mon_actions_handler(mmsd, false); } return true; @@ -7537,6 +7555,8 @@ void MySQL_Monitor::monitor_gr_async_actions_handler( bool MySQL_Monitor::monitor_replication_lag_process_ready_tasks(const std::vector& mmsds) { + + std::list> mysql_servers; for (auto& mmsd : mmsds) { @@ -7561,9 +7581,9 @@ bool MySQL_Monitor::monitor_replication_lag_process_ready_tasks(const std::vecto proxy_error("Error after %lldms on server %s:%d : %s\n", (mmsd->t2 - mmsd->t1) / 1000, mmsd->hostname, mmsd->port, (mmsd->mysql_error_msg ? mmsd->mysql_error_msg : "")); #endif } -#ifdef DEBUG - My_Conn_Pool->conn_unregister(mmsd); -#endif +//#ifdef DEBUG +// My_Conn_Pool->conn_unregister(mmsd); +//#endif mysql_close(mmsd->mysql); mmsd->mysql = NULL; } @@ -7591,13 +7611,18 @@ bool MySQL_Monitor::monitor_replication_lag_process_ready_tasks(const std::vecto int j = -1; num_fields = mysql_num_fields(mmsd->result); fields = mysql_fetch_fields(mmsd->result); +#ifdef TEST_REPLICATIONLAG + if (fields && num_fields == 1) +#else if ( fields && ( (num_fields == 1 && mmsd->use_percona_heartbeat == true) || (num_fields > 30 && mmsd->use_percona_heartbeat == false) ) - ) { + ) +#endif + { for (k = 0; k < num_fields; k++) { if (fields[k].name) { if (strcmp("Seconds_Behind_Master", fields[k].name) == 0) { @@ -7638,10 +7663,14 @@ bool MySQL_Monitor::monitor_replication_lag_process_ready_tasks(const std::vecto SAFE_SQLITE3_STEP2(statement); rc = (*proxy_sqlite3_clear_bindings)(statement); ASSERT_SQLITE_OK(rc, mmsd->mondb); rc = (*proxy_sqlite3_reset)(statement); ASSERT_SQLITE_OK(rc, mmsd->mondb); - MyHGM->replication_lag_action(mmsd->hostgroup_id, mmsd->hostname, mmsd->port, repl_lag); + //MyHGM->replication_lag_action(mmsd->hostgroup_id, mmsd->hostname, mmsd->port, repl_lag); (*proxy_sqlite3_finalize)(statement); + mysql_servers.push_back({ mmsd->hostgroup_id, mmsd->hostname, mmsd->port, repl_lag }); } + //executing replication lag action + MyHGM->replication_lag_action(mysql_servers); + return true; } @@ -7720,9 +7749,9 @@ bool MySQL_Monitor::monitor_galera_process_ready_tasks(const std::vectormysql, mmsd->mysql->net.fd, (mmsd->mysql_error_msg ? mmsd->mysql_error_msg : "")); #endif } -#ifdef DEBUG - My_Conn_Pool->conn_unregister(mmsd); -#endif // DEBUG +//#ifdef DEBUG +// My_Conn_Pool->conn_unregister(mmsd); +//#endif // DEBUG mysql_close(mmsd->mysql); mmsd->mysql = NULL; } diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index a34536f139..c80b5247a3 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -1,4 +1,5 @@ #include // std::cout +#include // std::stringstream #include #include // std::sort #include @@ -235,14 +236,6 @@ int sqlite3_json_init( } while (rc!=SQLITE_DONE);\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) typedef struct _arg_mysql_adm_t { struct sockaddr * addr; @@ -671,7 +664,8 @@ static char * admin_variables_names[]= { (char *)"web_verbosity", (char *)"prometheus_memory_metrics_interval", #ifdef DEBUG - (char *)"debug", + (char *)"debug", + (char *)"debug_output", #endif /* DEBUG */ NULL }; @@ -3260,10 +3254,10 @@ bool ProxySQL_Admin::GenericRefreshStatistics(const char *query_no_space, unsign if (stats_mysql_processlist) stats___mysql_processlist(); if (stats_mysql_query_digest_reset) { - stats___mysql_query_digests(true, stats_mysql_query_digest); + stats___mysql_query_digests_v2(true, stats_mysql_query_digest, false); } else { if (stats_mysql_query_digest) { - stats___mysql_query_digests(false); + stats___mysql_query_digests_v2(false, false, false); } } if (stats_mysql_errors) @@ -3658,6 +3652,7 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { if (sess->session_type == PROXYSQL_SESSION_ADMIN) { // no stats if (!strncasecmp("LOGENTRY ", query_no_space, strlen("LOGENTRY "))) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Received command LOGENTRY: %s\n", query_no_space + strlen("LOGENTRY ")); proxy_info("Received command LOGENTRY: %s\n", query_no_space + strlen("LOGENTRY ")); SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, NULL); run_query=false; @@ -4042,6 +4037,50 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { run_query=false; free(msg); break; + case 22: + // get all the entries from the digest map, but WRITING to DB + // it uses multiple threads + // It locks the maps while generating the resultset + r1 = SPA->stats___mysql_query_digests(false, true); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; + case 23: + // get all the entries from the digest map, but WRITING to DB + // it uses multiple threads for creating the resultset + r1 = SPA->stats___mysql_query_digests_v2(false, false, true); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; + case 24: + // get all the entries from the digest map, but WRITING to DB + // Do not create a resultset, uses the digest_umap + r1 = SPA->stats___mysql_query_digests_v2(false, false, false); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; + case 25: + // get all the entries from the digest map AND RESET, but WRITING to DB + // it uses multiple threads + // It locks the maps while generating the resultset + r1 = SPA->stats___mysql_query_digests(true, true); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; + case 26: + // get all the entries from the digest map AND RESET, but WRITING to DB + // it uses multiple threads for creating the resultset + r1 = SPA->stats___mysql_query_digests_v2(true, true, true); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; + case 27: + // get all the entries from the digest map AND RESET, but WRITING to DB + // Do not create a resultset, uses the digest_umap + r1 = SPA->stats___mysql_query_digests_v2(true, true, false); + SPA->send_MySQL_OK(&sess->client_myds->myprot, NULL, r1); + run_query=false; + break; case 31: { if (test_arg1==0) { @@ -5689,6 +5728,7 @@ ProxySQL_Admin::ProxySQL_Admin() : serial_exposer(std::function { update_modules_metrics }) { #ifdef DEBUG + debugdb_disk = NULL; if (glovars.has_debug==false) { #else if (glovars.has_debug==true) { @@ -5803,6 +5843,8 @@ ProxySQL_Admin::ProxySQL_Admin() : variables.p_memory_metrics_interval = 61; #ifdef DEBUG variables.debug=GloVars.global.gdbg; + debug_output = 1; + proxysql_set_admin_debug_output(debug_output); #endif /* DEBUG */ last_p_memory_metrics_ts = 0; @@ -6091,6 +6133,32 @@ bool ProxySQL_Admin::init() { #ifdef DEBUG admindb->execute("ATTACH DATABASE 'file:mem_mydb?mode=memory&cache=shared' AS myhgm"); admindb->execute("ATTACH DATABASE 'file:mem_monitor_internal_db?mode=memory&cache=shared' AS 'monitor_internal'"); + { + string debugdb_disk_path = string(GloVars.datadir) + "/" + "proxysql_debug.db"; + debugdb_disk = new SQLite3DB(); + debugdb_disk->open((char *)debugdb_disk_path.c_str(), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + debugdb_disk->execute("CREATE TABLE IF NOT EXISTS debug_log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , time INT NOT NULL , lapse INT NOT NULL , thread INT NOT NULL , file VARCHAR NOT NULL , line INT NOT NULL , funct VARCHAR NOT NULL , modnum INT NOT NULL , modname VARCHAR NOT NULL , verbosity INT NOT NULL , message VARCHAR , note VARCHAR , backtrace VARCHAR)"); +/* + // DO NOT CREATE INDEX. + // We can create index on a running instance or an archived DB if needed + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_time ON debug_log (time)"); + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_thread ON debug_log (thread)"); + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_file ON debug_log (file)"); + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_file_line ON debug_log (file,line)"); + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_funct ON debug_log (funct)"); + debugdb_disk->execute("CREATE INDEX IF NOT EXISTS idx_debug_log_modnum ON debug_log (modnum)"); +*/ + debugdb_disk->execute("PRAGMA synchronous=0"); +/* + // DO NOT ATTACH DATABASE + // it seems sqlite starts randomly failing. For example these 2 TAP tests: + // - admin_show_fields_from-t + // - admin_show_table_status-t + string cmd = "ATTACH DATABASE '" + debugdb_disk_path + "' AS debugdb_disk"; + admindb->execute(cmd.c_str()); +*/ + proxysql_set_admin_debugdb_disk(debugdb_disk); + } #endif /* DEBUG */ #ifdef DEBUG @@ -6230,6 +6298,10 @@ void ProxySQL_Admin::admin_shutdown() { delete configdb; delete monitordb; delete statsdb_disk; +#ifdef DEBUG + proxysql_set_admin_debugdb_disk(NULL); + delete debugdb_disk; +#endif (*proxy_sqlite3_shutdown)(); if (main_poll_fds) { for (i=0;i::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { SQLite3_row *r=*it; - bool rc=set_variable(r->fields[0],r->fields[1]); + bool rc=set_variable(r->fields[0],r->fields[1], lock); if (rc==false) { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); if (replace) { @@ -6529,7 +6603,9 @@ void ProxySQL_Admin::flush_admin_variables___database_to_runtime(SQLite3DB *db, } } //commit(); NOT IMPLEMENTED - if (checksum_variables.checksum_admin_variables) { + + // Checksums are always generated - 'admin-checksum_*' deprecated + { pthread_mutex_lock(&GloVars.checksum_mutex); // generate checksum for cluster flush_admin_variables___runtime_to_database(admindb, false, false, false, true); @@ -6876,7 +6952,9 @@ void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db, free(previous_default_collation_connection); GloMTH->commit(); GloMTH->wrunlock(); - if (checksum_variables.checksum_mysql_variables) { + + // Checksums are always generated - 'admin-checksum_*' deprecated + { // NOTE: 'GloMTH->wrunlock()' should have been called before this point to avoid possible // deadlocks. See issue #3847. pthread_mutex_lock(&GloVars.checksum_mutex); @@ -7545,8 +7623,8 @@ void ProxySQL_Admin::flush_ldap_variables___database_to_runtime(SQLite3DB *db, b } GloMyLdapAuth->wrunlock(); - // update variables checksum - if (checksum_variables.checksum_ldap_variables) { + // Checksums are always generated - 'admin-checksum_*' deprecated + { pthread_mutex_lock(&GloVars.checksum_mutex); // generate checksum for cluster flush_ldap_variables___runtime_to_database(admindb, false, false, false, true); @@ -7825,6 +7903,10 @@ char * ProxySQL_Admin::get_variable(char *name) { if (!strcasecmp(name,"debug")) { return strdup((variables.debug ? "true" : "false")); } + if (!strcasecmp(name,"debug_output")) { + sprintf(intbuf, "%d", debug_output); + return strdup(intbuf); + } #endif /* DEBUG */ return NULL; } @@ -7876,7 +7958,7 @@ void ProxySQL_Admin::delete_credentials(char *credentials) { free_tokenizer( &tok ); } -bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the public function, accessible from admin +bool ProxySQL_Admin::set_variable(char *name, char *value, bool lock) { // this is the public function, accessible from admin size_t vallen=strlen(value); if (!strcasecmp(name,"admin_credentials")) { @@ -8097,6 +8179,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_mysql_query_rules_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_mysql_query_rules_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_admin_variables_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_mysql_query_rules_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_mysql_query_rules_diffs_before_sync, intv); return true; @@ -8107,6 +8193,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_mysql_servers_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_mysql_servers_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_mysql_servers_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_mysql_servers_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_mysql_servers_diffs_before_sync, intv); return true; @@ -8117,6 +8207,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_mysql_users_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_mysql_users_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_mysql_users_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_mysql_users_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_mysql_users_diffs_before_sync, intv); return true; @@ -8127,6 +8221,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_proxysql_servers_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_proxysql_servers_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_proxysql_servers_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_proxysql_servers_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_proxysql_servers_diffs_before_sync, intv); return true; @@ -8137,6 +8235,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_mysql_variables_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_mysql_variables_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_mysql_variables_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_mysql_variables_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_mysql_variables_diffs_before_sync, intv); return true; @@ -8147,6 +8249,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_admin_variables_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_admin_variables_diffs_before_sync == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_admin_variables_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_admin_variables_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_admin_variables_diffs_before_sync, intv); return true; @@ -8157,6 +8263,10 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub if (!strcasecmp(name,"cluster_ldap_variables_diffs_before_sync")) { int intv=atoi(value); if (intv >= 0 && intv <= 1000) { + if (variables.cluster_ldap_variables_save_to_disk == 0 && intv != 0) { + proxy_info("Re-enabled previously disabled 'admin-cluster_ldap_variables_diffs_before_sync'. Resetting global checksums to force Cluster re-sync.\n"); + GloProxyCluster->Reset_Global_Checksums(lock); + } variables.cluster_ldap_variables_diffs_before_sync=intv; __sync_lock_test_and_set(&GloProxyCluster->cluster_ldap_variables_diffs_before_sync, intv); return true; @@ -8351,6 +8461,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_mysql_query_rules=false; + variables.cluster_mysql_query_rules_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_mysql_query_rules', setting 'admin-cluster_mysql_query_rules_diffs_before_sync=0'\n"); return true; } return false; @@ -8362,8 +8474,11 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_mysql_servers=false; + variables.cluster_mysql_servers_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_mysql_servers', setting 'admin-cluster_mysql_servers_diffs_before_sync=0'\n"); return true; } + return false; } if (!strcasecmp(name,"checksum_mysql_users")) { @@ -8373,6 +8488,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_mysql_users=false; + variables.cluster_mysql_users_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_mysql_users', setting 'admin-cluster_mysql_users_diffs_before_sync=0'\n"); return true; } return false; @@ -8384,6 +8501,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_mysql_variables=false; + variables.cluster_mysql_variables_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_mysql_variables', setting 'admin-cluster_mysql_variables_diffs_before_sync=0'\n"); return true; } return false; @@ -8395,6 +8514,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_admin_variables=false; + variables.cluster_admin_variables_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_admin_variables', setting 'admin-cluster_admin_variables_diffs_before_sync=0'\n"); return true; } return false; @@ -8406,6 +8527,8 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } if (strcasecmp(value,"false")==0 || strcasecmp(value,"0")==0) { checksum_variables.checksum_ldap_variables=false; + variables.cluster_ldap_variables_diffs_before_sync = 0; + proxy_warning("Disabling deprecated 'admin-checksum_ldap_variables', setting 'admin-cluster_ldap_variables_diffs_before_sync=0'\n"); return true; } return false; @@ -8444,6 +8567,17 @@ bool ProxySQL_Admin::set_variable(char *name, char *value) { // this is the pub } return false; } + if (!strcasecmp(name,"debug_output")) { + const auto fval = atoi(value); + if (fval > 0 && fval <= 3) { + debug_output = fval; + proxysql_set_admin_debug_output(debug_output); + return true; + } else { + return false; + } + return false; + } #endif /* DEBUG */ return false; } @@ -9266,7 +9400,22 @@ void ProxySQL_Admin::stats___proxysql_servers_checksums() { statsdb->execute("BEGIN"); statsdb->execute("DELETE FROM stats_proxysql_servers_checksums"); SQLite3_result *resultset=NULL; + // NOTE: This mutex unlock is required due to a race condition created when: + // - One Admin session has the following callstack: + // + admin_session_handler -> locks on 'sql_query_global_mutex' + // | GenericRefreshStatistics + // | stats___proxysql_servers_checksums + // | get_stats_proxysql_servers_checksums + // + stats_proxysql_servers_checksums -> tries to lock on 'ProxySQL_Cluster_Nodes::mutex' + // - One ProxySQL_Cluster thread has the following callstack: + // + ProxySQL_Cluster::Update_Node_Checksums + // + ProxySQL_Cluster_Nodes::Update_Node_Checksums -> locks on 'ProxySQL_Cluster_Nodes::mutex' + // | ProxySQL_Node_Entry::set_checksums + // + ProxySQL_Cluster::pull_mysql_query_rules_from_peer -> tries to lock on 'sql_query_global_mutex' + // Producing a deadlock scenario between the two threads. + pthread_mutex_unlock(&this->sql_query_global_mutex); resultset=GloProxyCluster->get_stats_proxysql_servers_checksums(); + pthread_mutex_lock(&this->sql_query_global_mutex); if (resultset) { int rc; sqlite3_stmt *statement1=NULL; @@ -9412,15 +9561,120 @@ void ProxySQL_Admin::stats___proxysql_message_metrics(bool reset) { delete resultset; } -void ProxySQL_Admin::stats___mysql_query_digests(bool reset, bool copy) { - if (!GloQPro) return; +int ProxySQL_Admin::stats___save_mysql_query_digest_to_sqlite( + const bool reset, const bool copy, const SQLite3_result *resultset, const umap_query_digest *digest_umap, + const umap_query_digest_text *digest_text_umap +) { + statsdb->execute("BEGIN"); + int rc; + sqlite3_stmt *statement1=NULL; + sqlite3_stmt *statement32=NULL; + char *query1=NULL; + char *query32=NULL; + std::string query32s = ""; + statsdb->execute("DELETE FROM stats_mysql_query_digest_reset"); + statsdb->execute("DELETE FROM stats_mysql_query_digest"); + if (reset) { + query1=(char *)"INSERT INTO stats_mysql_query_digest_reset VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)"; + query32s = "INSERT INTO stats_mysql_query_digest_reset VALUES " + generate_multi_rows_query(32,14); + query32 = (char *)query32s.c_str(); + } else { + query1=(char *)"INSERT INTO stats_mysql_query_digest VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)"; + query32s = "INSERT INTO stats_mysql_query_digest VALUES " + generate_multi_rows_query(32,14); + query32 = (char *)query32s.c_str(); + } + + rc = statsdb->prepare_v2(query1, &statement1); + ASSERT_SQLITE_OK(rc, statsdb); + rc = statsdb->prepare_v2(query32, &statement32); + ASSERT_SQLITE_OK(rc, statsdb); + int row_idx=0; + int num_rows = resultset ? resultset->rows_count : digest_umap->size(); + int max_bulk_row_idx = num_rows/32; + max_bulk_row_idx=max_bulk_row_idx*32; + auto it = resultset ? (std::unordered_map::iterator)NULL : digest_umap->cbegin(); + int i = 0; + // If the function do not receives a resultset, it gets the values directly from the digest_umap + while (resultset ? i != resultset->rows_count : it != digest_umap->end()) { + QP_query_digest_stats *qds = (QP_query_digest_stats *)(resultset ? NULL : it->second); + SQLite3_row *row = resultset ? resultset->rows[i] : NULL; + string digest_hex_str; + if (!resultset) { + std::ostringstream digest_stream; + digest_stream << "0x" << std::hex << qds->digest; + digest_hex_str = digest_stream.str(); + } + int idx=row_idx%32; + if (row_idxfields[11]) : qds->hid); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*14)+2, resultset ? row->fields[0] : qds->schemaname, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*14)+3, resultset ? row->fields[1] : qds->username, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*14)+4, resultset ? row->fields[2] : qds->client_address, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*14)+5, resultset ? row->fields[3] : digest_hex_str.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement32, (idx*14)+6, resultset ? row->fields[4] : qds->get_digest_text(digest_text_umap), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+7, resultset ? atoll(row->fields[5]) : qds->count_star); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+8, resultset ? atoll(row->fields[6]) : qds->first_seen); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+9, resultset ? atoll(row->fields[7]) : qds->last_seen); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+10, resultset ? atoll(row->fields[8]) : qds->sum_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+11, resultset ? atoll(row->fields[9]) : qds->min_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+12, resultset ? atoll(row->fields[10]) : qds->max_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+13, resultset ? atoll(row->fields[12]) : qds->rows_affected); ASSERT_SQLITE_OK(rc, statsdb); // rows affected + rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*14)+14, resultset ? atoll(row->fields[13]) : qds->rows_sent); ASSERT_SQLITE_OK(rc, statsdb); // rows sent + if (idx==31) { + SAFE_SQLITE3_STEP2(statement32); + rc=(*proxy_sqlite3_clear_bindings)(statement32); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_reset)(statement32); ASSERT_SQLITE_OK(rc, statsdb); + } + } else { // single row + rc=(*proxy_sqlite3_bind_int64)(statement1, 1, resultset ? atoll(row->fields[11]) : qds->hid); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement1, 2, resultset ? row->fields[0] : qds->schemaname, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement1, 3, resultset ? row->fields[1] : qds->username, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement1, 4, resultset ? row->fields[2] : qds->client_address, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement1, 5, resultset ? row->fields[3] : digest_hex_str.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_text)(statement1, 6, resultset ? row->fields[4] : qds->get_digest_text(digest_text_umap), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 7, resultset ? atoll(row->fields[5]) : qds->count_star); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 8, resultset ? atoll(row->fields[6]) : qds->first_seen); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 9, resultset ? atoll(row->fields[7]) : qds->last_seen); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 10, resultset ? atoll(row->fields[8]) : qds->sum_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 11, resultset ? atoll(row->fields[9]) : qds->min_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 12, resultset ? atoll(row->fields[10]) : qds->max_time); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 13, resultset ? atoll(row->fields[12]) : qds->rows_affected); ASSERT_SQLITE_OK(rc, statsdb); // rows affected + rc=(*proxy_sqlite3_bind_int64)(statement1, 14, resultset ? atoll(row->fields[13]) : qds->rows_sent); ASSERT_SQLITE_OK(rc, statsdb); // rows sent + SAFE_SQLITE3_STEP2(statement1); + rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, statsdb); + } +#ifdef DEBUG + if (resultset) + assert(row_idx == i); +#endif + row_idx++; + if (resultset) + i++; + else + it++; + } + (*proxy_sqlite3_finalize)(statement1); + (*proxy_sqlite3_finalize)(statement32); + if (reset) { + if (copy) { + statsdb->execute("INSERT INTO stats_mysql_query_digest SELECT * FROM stats_mysql_query_digest_reset"); + } + } + statsdb->execute("COMMIT"); + + return row_idx; +} + +int ProxySQL_Admin::stats___mysql_query_digests(bool reset, bool copy) { + if (!GloQPro) return 0; SQLite3_result * resultset=NULL; if (reset==true) { resultset=GloQPro->get_query_digests_reset(); } else { resultset=GloQPro->get_query_digests(); } - if (resultset==NULL) return; + if (resultset==NULL) return 0; statsdb->execute("BEGIN"); int rc; sqlite3_stmt *statement1=NULL; @@ -9515,6 +9769,26 @@ void ProxySQL_Admin::stats___mysql_query_digests(bool reset, bool copy) { } statsdb->execute("COMMIT"); delete resultset; + + return row_idx; +} + +int ProxySQL_Admin::stats___mysql_query_digests_v2(bool reset, bool copy, bool use_resultset) { + if (!GloQPro) return 0; + std::pair res; + if (reset == true) { + res = GloQPro->get_query_digests_reset_v2(copy, use_resultset); + } else { + res = GloQPro->get_query_digests_v2(use_resultset); + } + + if (res.first == NULL) + return res.second; + + int num_rows = GloAdmin->stats___save_mysql_query_digest_to_sqlite(reset, copy, res.first, NULL, NULL); + delete res.first; + + return num_rows; } void ProxySQL_Admin::stats___mysql_client_host_cache(bool reset) { @@ -10526,14 +10800,9 @@ void ProxySQL_Admin::add_admin_users() { void ProxySQL_Admin::__refresh_users( unique_ptr&& mysql_users_resultset, const string& checksum, const time_t epoch ) { - bool calculate_checksum = false; bool no_resultset_supplied = mysql_users_resultset == nullptr; - - if (checksum_variables.checksum_mysql_users) { - calculate_checksum = true; - } - if (calculate_checksum) - pthread_mutex_lock(&GloVars.checksum_mutex); + // Checksums are always generated - 'admin-checksum_*' deprecated + pthread_mutex_lock(&GloVars.checksum_mutex); __delete_inactive_users(USERNAME_BACKEND); __delete_inactive_users(USERNAME_FRONTEND); @@ -10553,7 +10822,8 @@ void ProxySQL_Admin::__refresh_users( GloMyAuth->remove_inactives(USERNAME_FRONTEND); set_variable((char *)"admin_credentials",(char *)""); - if (calculate_checksum) { + // Checksums are always generated - 'admin-checksum_*' deprecated + { char* buff = nullptr; char buf[20] = { 0 }; @@ -10590,8 +10860,9 @@ void ProxySQL_Admin::__refresh_users( // store the new 'added_users' resultset after generating the new checksum GloMyAuth->save_mysql_users(std::move(mysql_users_resultset)); - pthread_mutex_unlock(&GloVars.checksum_mutex); } + pthread_mutex_unlock(&GloVars.checksum_mutex); + proxy_info( "Computed checksum for 'LOAD MYSQL USERS TO RUNTIME' was '%s', with epoch '%llu'\n", GloVars.checksums_values.mysql_users.checksum, GloVars.checksums_values.mysql_users.epoch @@ -12132,7 +12403,9 @@ char* ProxySQL_Admin::load_mysql_query_rules_to_runtime(SQLite3_result* SQLite3_ #ifdef BENCHMARK_FASTROUTING_LOAD for (int i=0; i<10; i++) { #endif // BENCHMARK_FASTROUTING_LOAD - if (checksum_variables.checksum_mysql_query_rules) { + + // Checksums are always generated - 'admin-checksum_*' deprecated + { pthread_mutex_lock(&GloVars.checksum_mutex); char* buff = nullptr; char buf[20]; @@ -13429,6 +13702,18 @@ void ProxySQL_Admin::enable_readonly_testing() { } #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG +void ProxySQL_Admin::enable_replicationlag_testing() { + proxy_info("Admin is enabling Replication Lag Testing using SQLite3 Server and HGs from 5201 to 5800\n"); + mysql_servers_wrlock(); + + admindb->execute("DELETE FROM mysql_servers WHERE hostgroup_id BETWEEN 5201 AND 5800"); + + load_mysql_servers_to_runtime(); + mysql_servers_wrunlock(); +} +#endif // TEST_REPLICATIONLAG + void ProxySQL_Admin::ProxySQL_Test___MySQL_HostGroups_Manager_generate_many_clusters() { mysql_servers_wrlock(); admindb->execute("DELETE FROM mysql_servers WHERE hostgroup_id BETWEEN 10001 AND 20000"); diff --git a/lib/ProxySQL_Cluster.cpp b/lib/ProxySQL_Cluster.cpp index c70c3b8959..0db4a20b69 100644 --- a/lib/ProxySQL_Cluster.cpp +++ b/lib/ProxySQL_Cluster.cpp @@ -33,15 +33,6 @@ } while (rc!=SQLITE_DONE);\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) - using std::vector; using std::pair; using std::string; @@ -425,9 +416,22 @@ ProxySQL_Node_Metrics * ProxySQL_Node_Entry::get_metrics_prev() { void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { MYSQL_ROW row; time_t now = time(NULL); + + // Fetch the cluster_*_diffs_before_sync variables to ensure consistency at local scope + unsigned int diff_mqr = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_query_rules_diffs_before_sync,0); + unsigned int diff_ms = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_servers_diffs_before_sync,0); + unsigned int diff_mu = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_users_diffs_before_sync,0); + unsigned int diff_ps = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_proxysql_servers_diffs_before_sync,0); + unsigned int diff_mv = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_variables_diffs_before_sync,0); + unsigned int diff_lv = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_ldap_variables_diffs_before_sync,0); + unsigned int diff_av = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_admin_variables_diffs_before_sync,0); + pthread_mutex_lock(&GloVars.checksum_mutex); + while ( _r && (row = mysql_fetch_row(_r))) { if (strcmp(row[0],"admin_variables")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.admin_variables; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.admin_variables; checksums_values.admin_variables.version = atoll(row[1]); checksums_values.admin_variables.epoch = atoll(row[2]); checksums_values.admin_variables.last_updated = now; @@ -435,9 +439,26 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.admin_variables.checksum, row[3]); checksums_values.admin_variables.last_changed = now; checksums_values.admin_variables.diff_check = 1; - proxy_info("Cluster: detected a new checksum for admin_variables from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.admin_variables.version, checksums_values.admin_variables.epoch, checksums_values.admin_variables.checksum); + const char* no_sync_message = NULL; + + if (diff_av) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_admin_variables_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); + } } else { - proxy_info("Cluster: checksum for admin_variables from peer %s:%d matches with local checksum %s, we won't sync.\n", hostname, port, GloVars.checksums_values.admin_variables.checksum); checksums_values.admin_variables.diff_check++; } if (strcmp(checksums_values.admin_variables.checksum, GloVars.checksums_values.admin_variables.checksum) == 0) { @@ -446,6 +467,8 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { continue; } if (strcmp(row[0],"mysql_query_rules")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.mysql_query_rules; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.mysql_query_rules; checksums_values.mysql_query_rules.version = atoll(row[1]); checksums_values.mysql_query_rules.epoch = atoll(row[2]); checksums_values.mysql_query_rules.last_updated = now; @@ -453,9 +476,24 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.mysql_query_rules.checksum, row[3]); checksums_values.mysql_query_rules.last_changed = now; checksums_values.mysql_query_rules.diff_check = 1; - proxy_info("Cluster: detected a new checksum for mysql_query_rules from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.mysql_query_rules.version, checksums_values.mysql_query_rules.epoch, checksums_values.mysql_query_rules.checksum); - if (strcmp(checksums_values.mysql_query_rules.checksum, GloVars.checksums_values.mysql_query_rules.checksum) == 0) { - proxy_info("Cluster: checksum for mysql_query_rules from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.mysql_query_rules.checksum); + const char* no_sync_message = NULL; + + if (diff_mqr) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_mysql_query_rules_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.mysql_query_rules.diff_check++; @@ -466,6 +504,8 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { continue; } if (strcmp(row[0],"mysql_servers")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.mysql_servers; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.mysql_servers; checksums_values.mysql_servers.version = atoll(row[1]); checksums_values.mysql_servers.epoch = atoll(row[2]); checksums_values.mysql_servers.last_updated = now; @@ -473,19 +513,43 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.mysql_servers.checksum, row[3]); checksums_values.mysql_servers.last_changed = now; checksums_values.mysql_servers.diff_check = 1; - proxy_info("Cluster: detected a new checksum for mysql_servers from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.mysql_servers.version, checksums_values.mysql_servers.epoch, checksums_values.mysql_servers.checksum); - if (strcmp(checksums_values.mysql_servers.checksum, GloVars.checksums_values.mysql_servers.checksum) == 0) { - proxy_info("Cluster: checksum for mysql_servers from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.mysql_servers.checksum); + const char* no_sync_message = NULL; + + if (diff_ms) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_mysql_servers_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.mysql_servers.diff_check++; } if (strcmp(checksums_values.mysql_servers.checksum, GloVars.checksums_values.mysql_servers.checksum) == 0) { + // See LOGGING-NOTE at 'admin_variables' above. + if (checksums_values.mysql_servers.last_changed == now) { + proxy_info( + "Cluster: checksum for mysql_servers from peer %s:%d matches with local checksum %s , we won't sync.\n", + hostname, port, GloVars.checksums_values.mysql_servers.checksum + ); + } checksums_values.mysql_servers.diff_check = 0; } continue; } if (strcmp(row[0],"mysql_users")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.mysql_users; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.mysql_users; checksums_values.mysql_users.version = atoll(row[1]); checksums_values.mysql_users.epoch = atoll(row[2]); checksums_values.mysql_users.last_updated = now; @@ -493,9 +557,24 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.mysql_users.checksum, row[3]); checksums_values.mysql_users.last_changed = now; checksums_values.mysql_users.diff_check = 1; - proxy_info("Cluster: detected a new checksum for mysql_users from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.mysql_users.version, checksums_values.mysql_users.epoch, checksums_values.mysql_users.checksum); - if (strcmp(checksums_values.mysql_users.checksum, GloVars.checksums_values.mysql_users.checksum) == 0) { - proxy_info("Cluster: checksum for mysql_users from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.mysql_users.checksum); + const char* no_sync_message = NULL; + + if (diff_mu) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_mysql_users_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.mysql_users.diff_check++; @@ -506,6 +585,8 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { continue; } if (strcmp(row[0],"mysql_variables")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.mysql_variables; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.mysql_variables; checksums_values.mysql_variables.version = atoll(row[1]); checksums_values.mysql_variables.epoch = atoll(row[2]); checksums_values.mysql_variables.last_updated = now; @@ -513,9 +594,24 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.mysql_variables.checksum, row[3]); checksums_values.mysql_variables.last_changed = now; checksums_values.mysql_variables.diff_check = 1; - proxy_info("Cluster: detected a new checksum for mysql_variables from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.mysql_variables.version, checksums_values.mysql_variables.epoch, checksums_values.mysql_variables.checksum); - if (strcmp(checksums_values.mysql_variables.checksum, GloVars.checksums_values.mysql_variables.checksum) == 0) { - proxy_info("Cluster: checksum for mysql_variables from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.mysql_variables.checksum); + const char* no_sync_message = NULL; + + if (diff_mv) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_mysql_variables_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.mysql_variables.diff_check++; @@ -526,6 +622,8 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { continue; } if (strcmp(row[0],"proxysql_servers")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.proxysql_servers; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.proxysql_servers; checksums_values.proxysql_servers.version = atoll(row[1]); checksums_values.proxysql_servers.epoch = atoll(row[2]); checksums_values.proxysql_servers.last_updated = now; @@ -533,9 +631,24 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.proxysql_servers.checksum, row[3]); checksums_values.proxysql_servers.last_changed = now; checksums_values.proxysql_servers.diff_check = 1; - proxy_info("Cluster: detected a new checksum for proxysql_servers from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.proxysql_servers.version, checksums_values.proxysql_servers.epoch, checksums_values.proxysql_servers.checksum); - if (strcmp(checksums_values.proxysql_servers.checksum, GloVars.checksums_values.proxysql_servers.checksum) == 0) { - proxy_info("Cluster: checksum for proxysql_servers from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.proxysql_servers.checksum); + const char* no_sync_message = NULL; + + if (diff_ps) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_proxysql_servers_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.proxysql_servers.diff_check++; @@ -546,6 +659,8 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { continue; } if (GloMyLdapAuth && strcmp(row[0],"ldap_variables")==0) { + ProxySQL_Checksum_Value_2& checksum = checksums_values.ldap_variables; + ProxySQL_Checksum_Value& global_checksum = GloVars.checksums_values.ldap_variables; checksums_values.ldap_variables.version = atoll(row[1]); checksums_values.ldap_variables.epoch = atoll(row[2]); checksums_values.ldap_variables.last_updated = now; @@ -553,9 +668,24 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { strcpy(checksums_values.ldap_variables.checksum, row[3]); checksums_values.ldap_variables.last_changed = now; checksums_values.ldap_variables.diff_check = 1; - proxy_info("Cluster: detected a new checksum for ldap_variables from peer %s:%d, version %llu, epoch %llu, checksum %s . Not syncing yet ...\n", hostname, port, checksums_values.ldap_variables.version, checksums_values.ldap_variables.epoch, checksums_values.ldap_variables.checksum); - if (strcmp(checksums_values.ldap_variables.checksum, GloVars.checksums_values.ldap_variables.checksum) == 0) { - proxy_info("Cluster: checksum for ldap_variables from peer %s:%d matches with local checksum %s , we won't sync.\n", hostname, port, GloVars.checksums_values.ldap_variables.checksum); + const char* no_sync_message = NULL; + + if (diff_lv) { + no_sync_message = "Not syncing yet ...\n"; + } else { + no_sync_message = "Not syncing due to 'admin-cluster_ldap_variables_diffs_before_sync=0'.\n"; + } + + proxy_info( + "Cluster: detected a new checksum for %s from peer %s:%d, version %llu, epoch %llu, checksum %s . %s", + row[0], hostname, port, checksum.version, checksum.epoch, checksum.checksum, no_sync_message + ); + + if (strcmp(checksum.checksum, global_checksum.checksum) == 0) { + proxy_info( + "Cluster: checksum for %s from peer %s:%d matches with local checksum %s , we won't sync.\n", + row[0], hostname, port, global_checksum.checksum + ); } } else { checksums_values.ldap_variables.diff_check++; @@ -622,13 +752,6 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) { // we now do a series of checks, and we take action // note that this is done outside the critical section // as mutex on GloVars.checksum_mutex is already released - unsigned int diff_mqr = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_query_rules_diffs_before_sync,0); - unsigned int diff_ms = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_servers_diffs_before_sync,0); - unsigned int diff_mu = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_users_diffs_before_sync,0); - unsigned int diff_ps = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_proxysql_servers_diffs_before_sync,0); - unsigned int diff_mv = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_mysql_variables_diffs_before_sync,0); - unsigned int diff_lv = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_ldap_variables_diffs_before_sync,0); - unsigned int diff_av = (unsigned int)__sync_fetch_and_add(&GloProxyCluster->cluster_admin_variables_diffs_before_sync,0); ProxySQL_Checksum_Value_2 *v = NULL; if (diff_mqr) { unsigned long long own_version = __sync_fetch_and_add(&GloVars.checksums_values.mysql_query_rules.version,0); @@ -1999,7 +2122,7 @@ void ProxySQL_Cluster::pull_global_variables_from_peer(const string& var_type, c GloAdmin->flush_mysql_variables__from_memory_to_disk(); } } else if (var_type == "admin") { - GloAdmin->load_admin_variables_to_runtime(expected_checksum, epoch); + GloAdmin->load_admin_variables_to_runtime(expected_checksum, epoch, false); if (GloProxyCluster->cluster_admin_variables_save_to_disk == true) { proxy_info("Cluster: Saving to disk Admin Variables from peer %s:%d\n", hostname, port); @@ -2466,6 +2589,20 @@ bool ProxySQL_Cluster_Nodes::Update_Global_Checksum(char * _h, uint16_t _p, MYSQ return ret; } +void ProxySQL_Cluster_Nodes::Reset_Global_Checksums(bool lock) { + if (lock) { + pthread_mutex_lock(&mutex); + } + + for (auto& proxy_node_entry : umap_proxy_nodes) { + proxy_node_entry.second->global_checksum = 0; + } + + if (lock) { + pthread_mutex_unlock(&mutex); + } +} + // if it returns false , the node doesn't exist anymore and the monitor should stop bool ProxySQL_Cluster_Nodes::Update_Node_Metrics(char * _h, uint16_t _p, MYSQL_RES *_r, unsigned long long _response_time) { bool ret = false; @@ -2503,7 +2640,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_mysql_query_rules(char **host, uin if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_mqr) { + if (v->diff_check >= diff_mqr) { epoch = v->epoch; version = v->version; if (hostname) { @@ -2563,7 +2700,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_mysql_servers(char **host, uint16_ if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_ms) { + if (v->diff_check >= diff_ms) { epoch = v->epoch; version = v->version; if (pc) { @@ -2629,7 +2766,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_mysql_users(char **host, uint16_t if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_mu) { + if (v->diff_check >= diff_mu) { epoch = v->epoch; version = v->version; if (hostname) { @@ -2684,7 +2821,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_mysql_variables(char **host, uint1 if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_mu) { + if (v->diff_check >= diff_mu) { epoch = v->epoch; version = v->version; if (hostname) { @@ -2739,7 +2876,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_admin_variables(char **host, uint1 if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_mu) { + if (v->diff_check >= diff_mu) { epoch = v->epoch; version = v->version; if (hostname) { @@ -2793,7 +2930,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_ldap_variables(char **host, uint16 if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_mu) { + if (v->diff_check >= diff_mu) { epoch = v->epoch; version = v->version; if (hostname) { @@ -2849,7 +2986,7 @@ void ProxySQL_Cluster_Nodes::get_peer_to_sync_proxysql_servers(char **host, uint if (v->version > 1) { if ( v->epoch > epoch ) { max_epoch = v->epoch; - if (v->diff_check > diff_ps) { + if (v->diff_check >= diff_ps) { epoch = v->epoch; version = v->version; if (hostname) { diff --git a/lib/ProxySQL_GloVars.cpp b/lib/ProxySQL_GloVars.cpp index 1df01f5313..e07cdf6fcc 100644 --- a/lib/ProxySQL_GloVars.cpp +++ b/lib/ProxySQL_GloVars.cpp @@ -79,8 +79,13 @@ ProxySQL_GlobalVariables::~ProxySQL_GlobalVariables() { } }; +/** + * @brief ProxySQL_GlobalVariables constructor + * @details 'checksums_values' constructor is required to be explicitly called as it's encapsulated in a + * anonymous union. + */ ProxySQL_GlobalVariables::ProxySQL_GlobalVariables() : - prometheus_registry(std::make_shared()) + prometheus_registry(std::make_shared()), checksums_values() { confFile=NULL; __cmd_proxysql_config_file=NULL; diff --git a/lib/ProxySQL_Statistics.cpp b/lib/ProxySQL_Statistics.cpp index e0afeaeb8e..523623109f 100644 --- a/lib/ProxySQL_Statistics.cpp +++ b/lib/ProxySQL_Statistics.cpp @@ -32,16 +32,6 @@ extern MySQL_Threads_Handler *GloMTH; } while (rc!=SQLITE_DONE );\ } while (0) -#define SAFE_SQLITE3_STEP2(_stmt) do {\ - do {\ - rc=(*proxy_sqlite3_step)(_stmt);\ - if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\ - usleep(100);\ - }\ - } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\ -} while (0) - - ProxySQL_Statistics::ProxySQL_Statistics() { statsdb_mem = new SQLite3DB(); statsdb_mem->open((char *)"file:statsdb_mem?mode=memory&cache=shared", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); diff --git a/lib/Query_Processor.cpp b/lib/Query_Processor.cpp index debba9d220..22cc0367db 100644 --- a/lib/Query_Processor.cpp +++ b/lib/Query_Processor.cpp @@ -31,6 +31,7 @@ #include #include extern MySQL_Threads_Handler *GloMTH; +extern ProxySQL_Admin *GloAdmin; static int int_cmp(const void *a, const void *b) { const unsigned long long *ia = (const unsigned long long *)a; @@ -186,8 +187,11 @@ QP_query_digest_stats::QP_query_digest_stats(char *u, char *s, uint64_t d, char rows_sent=0; hid=h; } -void QP_query_digest_stats::add_time(unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs) { - count_star++; +void QP_query_digest_stats::add_time( + unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs, + unsigned long long cnt +) { + count_star += cnt; sum_time+=t; rows_affected+=ra; rows_sent+=rs; @@ -229,6 +233,30 @@ QP_query_digest_stats::~QP_query_digest_stats() { client_address=NULL; } } + +// Funtion to get the digest text associated to a QP_query_digest_stats. +// QP_query_digest_stats member type "char *digest_text" may by NULL, so we +// have to get the digest text from "digest_text_umap". +char *QP_query_digest_stats::get_digest_text(const umap_query_digest_text *digest_text_umap) { + char *digest_text_str = NULL; + + if (digest_text) { + digest_text_str = digest_text; + } else { + std::unordered_map::const_iterator it; + it = digest_text_umap->find(digest); + if (it != digest_text_umap->end()) { + digest_text_str = it->second; + } else { + // LCOV_EXCL_START + assert(0); + // LCOV_EXCL_STOP + } + } + + return digest_text_str; +} + char **QP_query_digest_stats::get_row(umap_query_digest_text *digest_text_umap, query_digest_stats_pointers_t *qdsp) { char **pta=qdsp->pta; @@ -244,19 +272,7 @@ char **QP_query_digest_stats::get_row(umap_query_digest_text *digest_text_umap, sprintf(qdsp->digest,"0x%016llX", (long long unsigned int)digest); pta[3]=qdsp->digest; - if (digest_text) { - pta[4]=digest_text; - } else { - std::unordered_map::iterator it; - it=digest_text_umap->find(digest); - if (it != digest_text_umap->end()) { - pta[4] = it->second; - } else { - // LCOV_EXCL_START - assert(0); - // LCOV_EXCL_STOP - } - } + pta[4] = get_digest_text(digest_text_umap); //sprintf(qdsp->count_star,"%u",count_star); my_itoa(qdsp->count_star, count_star); @@ -1033,51 +1049,46 @@ unsigned long long Query_Processor::purge_query_digests(bool async_purge, bool p unsigned long long Query_Processor::purge_query_digests_async(char **msg) { unsigned long long ret = 0; pthread_rwlock_wrlock(&digest_rwlock); + + + umap_query_digest digest_umap_aux; + umap_query_digest_text digest_text_umap_aux; + pthread_rwlock_wrlock(&digest_rwlock); + digest_umap.swap(digest_umap_aux); + digest_text_umap.swap(digest_text_umap_aux); + pthread_rwlock_unlock(&digest_rwlock); + int num_rows = 0; unsigned long long curtime1=monotonic_time(); - size_t map1_size = digest_umap.size(); - size_t map2_size = digest_text_umap.size(); + size_t map1_size = digest_umap_aux.size(); + size_t map2_size = digest_text_umap_aux.size(); ret = map1_size + map2_size; - unsigned long long i = 0; - QP_query_digest_stats **array1 = (QP_query_digest_stats **)malloc(sizeof(QP_query_digest_stats *)*map1_size); - char **array2 = (char **)malloc(sizeof(char *)*map2_size); - i=0; - for (std::unordered_map::iterator it=digest_umap.begin(); it!=digest_umap.end(); ++it) { - array1[i]=(QP_query_digest_stats *)it->second; - i++; - //delete qds; + + for ( + std::unordered_map::iterator it = digest_umap_aux.begin(); + it != digest_umap_aux.end(); + ++it + ) { + QP_query_digest_stats *qds = (QP_query_digest_stats *)it->second; + delete qds; } - i=0; - for (std::unordered_map::iterator it=digest_text_umap.begin(); it!=digest_text_umap.end(); ++it) { - array2[i] = it->second; - //free(it->second); - i++; + digest_umap_aux.clear(); + for (std::unordered_map::iterator it=digest_text_umap_aux.begin(); it!=digest_text_umap_aux.end(); ++it) { + free(it->second); } - digest_umap.erase(digest_umap.begin(),digest_umap.end()); - digest_text_umap.erase(digest_text_umap.begin(),digest_text_umap.end()); - pthread_rwlock_unlock(&digest_rwlock); - unsigned long long curtime2=monotonic_time(); - curtime1 = curtime1/1000; - curtime2 = curtime2/1000; + digest_text_umap_aux.clear(); + + if (map1_size >= DIGEST_STATS_FAST_MINSIZE) { - proxy_info("Purging stats_mysql_query_digest: locked for %llums to remove %lu entries\n", curtime2-curtime1, map1_size); - } - char buf[128]; - sprintf(buf, "Query digest map locked for %llums", curtime2-curtime1); - *msg = strdup(buf); - for (i=0; i Query_Processor::get_query_digests_v2(const bool use_resultset) { + proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 4, "Dumping current query digest\n"); + SQLite3_result *result = NULL; + // Create two auxiliary maps and swap its content with the main maps. This + // way, this function can read query digests stored until now while other + // threads write in the other map. We need to lock while swapping. + umap_query_digest digest_umap_aux, digest_umap_aux_2; + umap_query_digest_text digest_text_umap_aux, digest_text_umap_aux_2; + pthread_rwlock_wrlock(&digest_rwlock); + digest_umap.swap(digest_umap_aux); + digest_text_umap.swap(digest_text_umap_aux); + pthread_rwlock_unlock(&digest_rwlock); + int num_rows = 0; + unsigned long long curtime1; + unsigned long long curtime2; + size_t map_size = digest_umap_aux.size(); + curtime1 = monotonic_time(); // curtime1 must always be initialized + if (use_resultset) { + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + result = new SQLite3_result(14, true); + } else { + result = new SQLite3_result(14); + } + result->add_column_definition(SQLITE_TEXT,"hid"); + result->add_column_definition(SQLITE_TEXT,"schemaname"); + result->add_column_definition(SQLITE_TEXT,"username"); + result->add_column_definition(SQLITE_TEXT,"client_address"); + result->add_column_definition(SQLITE_TEXT,"digest"); + result->add_column_definition(SQLITE_TEXT,"digest_text"); + result->add_column_definition(SQLITE_TEXT,"count_star"); + result->add_column_definition(SQLITE_TEXT,"first_seen"); + result->add_column_definition(SQLITE_TEXT,"last_seen"); + result->add_column_definition(SQLITE_TEXT,"sum_time"); + result->add_column_definition(SQLITE_TEXT,"min_time"); + result->add_column_definition(SQLITE_TEXT,"max_time"); + result->add_column_definition(SQLITE_TEXT,"rows_affected"); + result->add_column_definition(SQLITE_TEXT,"rows_sent"); + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + int n=DIGEST_STATS_FAST_THREADS; + get_query_digests_parallel_args args[n]; + for (int i=0; i::iterator it = digest_umap_aux.begin(); + it != digest_umap_aux.end(); + ++it + ) { + QP_query_digest_stats *qds=(QP_query_digest_stats *)it->second; + query_digest_stats_pointers_t *a = (query_digest_stats_pointers_t *)malloc(sizeof(query_digest_stats_pointers_t)); + char **pta=qds->get_row(&digest_text_umap_aux, a); + result->add_row(pta); + free(a); + } + } + } else { + num_rows = GloAdmin->stats___save_mysql_query_digest_to_sqlite( + false, false, NULL, &digest_umap_aux, &digest_text_umap_aux + ); + } + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + curtime2=monotonic_time(); + curtime1 = curtime1/1000; + curtime2 = curtime2/1000; + proxy_info("Running query on stats_mysql_query_digest: (not locked) %llums to retrieve %lu entries\n", curtime2-curtime1, map_size); + } + + // Once we finish creating the resultset or writing to SQLite, we use a + // second group of auxiliary maps to swap it with the first group of + // auxiliary maps. This way, we can merge the main maps and the first + // auxiliary maps without locking the mutex during the process. This is + // useful because writing to SQLite can take a lot of time, so the first + // group of auxiliary maps could grow large. + pthread_rwlock_wrlock(&digest_rwlock); + digest_umap.swap(digest_umap_aux_2); + digest_text_umap.swap(digest_text_umap_aux_2); + pthread_rwlock_unlock(&digest_rwlock); + + // Once we do the swap, we merge the content of the first auxiliary maps + // in the main maps and clear the content of the auxiliary maps. + for (const auto& element : digest_umap_aux_2) { + uint64_t digest = element.first; + QP_query_digest_stats *qds = (QP_query_digest_stats *)element.second; + std::unordered_map::iterator it = digest_umap_aux.find(digest); + if (it != digest_umap_aux.end()) { + // found + QP_query_digest_stats *qds_equal = (QP_query_digest_stats *)it->second; + qds_equal->add_time( + qds->min_time, qds->last_seen, qds->rows_affected, qds->rows_sent, qds->count_star + ); + delete qds; + } else { + digest_umap_aux.insert(element); + } + } + digest_text_umap.insert(digest_text_umap_aux.begin(), digest_text_umap_aux.end()); + digest_umap_aux_2.clear(); + digest_text_umap_aux_2.clear(); + + // Once we finish merging the main maps and the first auxiliary maps, we + // lock and swap the main maps with the second auxiliary maps. Then, we + // merge the content of the auxiliary maps in the main maps and clear the + // content of the auxiliary maps. + pthread_rwlock_wrlock(&digest_rwlock); + digest_umap_aux.swap(digest_umap); + digest_text_umap_aux.swap(digest_text_umap); + for (const auto& element : digest_umap_aux) { + uint64_t digest = element.first; + QP_query_digest_stats *qds = (QP_query_digest_stats *)element.second; + std::unordered_map::iterator it = digest_umap.find(digest); + if (it != digest_umap.end()) { + // found + QP_query_digest_stats *qds_equal = (QP_query_digest_stats *)it->second; + qds_equal->add_time( + qds->min_time, qds->last_seen, qds->rows_affected, qds->rows_sent, qds->count_star + ); + delete qds; + } else { + digest_umap.insert(element); + } + } + digest_text_umap.insert(digest_text_umap_aux.begin(), digest_text_umap_aux.end()); + pthread_rwlock_unlock(&digest_rwlock); + digest_umap_aux.clear(); + digest_text_umap_aux.clear(); + + std::pair res{result, num_rows}; + return res; +} + SQLite3_result * Query_Processor::get_query_digests() { proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 4, "Dumping current query digest\n"); SQLite3_result *result = NULL; @@ -1180,9 +1336,9 @@ SQLite3_result * Query_Processor::get_query_digests() { unsigned long long curtime1; unsigned long long curtime2; size_t map_size = digest_umap.size(); + curtime1 = monotonic_time(); // curtime1 must always be initialized if (map_size >= DIGEST_STATS_FAST_MINSIZE) { result = new SQLite3_result(14, true); - curtime1 = monotonic_time(); } else { result = new SQLite3_result(14); } @@ -1240,6 +1396,122 @@ SQLite3_result * Query_Processor::get_query_digests() { return result; } +std::pair Query_Processor::get_query_digests_reset_v2( + const bool copy, const bool use_resultset +) { + SQLite3_result *result = NULL; + umap_query_digest digest_umap_aux; + umap_query_digest_text digest_text_umap_aux; + pthread_rwlock_wrlock(&digest_rwlock); + digest_umap.swap(digest_umap_aux); + digest_text_umap.swap(digest_text_umap_aux); + pthread_rwlock_unlock(&digest_rwlock); + int num_rows = 0; + unsigned long long curtime1; + unsigned long long curtime2; + size_t map_size = digest_umap_aux.size(); // we need to use the new map + bool free_me = false; + bool defer_free = false; + int n=DIGEST_STATS_FAST_THREADS; + get_query_digests_parallel_args args[n]; + curtime1 = monotonic_time(); // curtime1 must always be initialized + if (use_resultset) { + free_me = true; + defer_free = true; + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + result = new SQLite3_result(14, true); + } else { + result = new SQLite3_result(14); + } + result->add_column_definition(SQLITE_TEXT,"hid"); + result->add_column_definition(SQLITE_TEXT,"schemaname"); + result->add_column_definition(SQLITE_TEXT,"username"); + result->add_column_definition(SQLITE_TEXT,"client_address"); + result->add_column_definition(SQLITE_TEXT,"digest"); + result->add_column_definition(SQLITE_TEXT,"digest_text"); + result->add_column_definition(SQLITE_TEXT,"count_star"); + result->add_column_definition(SQLITE_TEXT,"first_seen"); + result->add_column_definition(SQLITE_TEXT,"last_seen"); + result->add_column_definition(SQLITE_TEXT,"sum_time"); + result->add_column_definition(SQLITE_TEXT,"min_time"); + result->add_column_definition(SQLITE_TEXT,"max_time"); + result->add_column_definition(SQLITE_TEXT,"rows_affected"); + result->add_column_definition(SQLITE_TEXT,"rows_sent"); + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + for (int i=0; i::iterator it=digest_umap_aux.begin(); it!=digest_umap_aux.end(); ++it) { + QP_query_digest_stats *qds=(QP_query_digest_stats *)it->second; + delete qds; + } + } + } else { + for (std::unordered_map::iterator it=digest_umap_aux.begin(); it!=digest_umap_aux.end(); ++it) { + QP_query_digest_stats *qds=(QP_query_digest_stats *)it->second; + query_digest_stats_pointers_t *a = (query_digest_stats_pointers_t *)malloc(sizeof(query_digest_stats_pointers_t)); + char **pta=qds->get_row(&digest_text_umap_aux, a); + result->add_row(pta); + free(a); + delete qds; + } + } + } else { + num_rows = GloAdmin->stats___save_mysql_query_digest_to_sqlite( + true, copy, result, &digest_umap_aux, &digest_text_umap_aux + ); + for ( + std::unordered_map::iterator it = digest_umap_aux.begin(); + it != digest_umap_aux.end(); + ++it + ) { + QP_query_digest_stats *qds = (QP_query_digest_stats *)it->second; + delete qds; + } + } + digest_umap_aux.clear(); + // this part is always single-threaded + for (std::unordered_map::iterator it=digest_text_umap_aux.begin(); it!=digest_text_umap_aux.end(); ++it) { + free(it->second); + } + digest_text_umap_aux.clear(); + if (map_size >= DIGEST_STATS_FAST_MINSIZE) { + curtime2=monotonic_time(); + curtime1 = curtime1/1000; + curtime2 = curtime2/1000; + proxy_info("Running query on stats_mysql_query_digest: (not locked) %llums to retrieve %lu entries\n", curtime2-curtime1, map_size); + if (free_me) { + if (defer_free) { + for (int i=0; i res{result, num_rows}; + return res; +} void Query_Processor::get_query_digests_reset(umap_query_digest *uqd, umap_query_digest_text *uqdt) { pthread_rwlock_wrlock(&digest_rwlock); @@ -1258,8 +1530,8 @@ SQLite3_result * Query_Processor::get_query_digests_reset() { int n=DIGEST_STATS_FAST_THREADS; get_query_digests_parallel_args args[n]; size_t map_size = digest_umap.size(); + curtime1 = monotonic_time(); // curtime1 must always be initialized if (map_size >= DIGEST_STATS_FAST_MINSIZE) { - curtime1=monotonic_time(); result = new SQLite3_result(14, true); } else { result = new SQLite3_result(14); diff --git a/lib/debug.cpp b/lib/debug.cpp index 54007fd92b..f19984eb38 100644 --- a/lib/debug.cpp +++ b/lib/debug.cpp @@ -3,6 +3,7 @@ #include "sqlite3db.h" #include "prometheus_helpers.h" +#include "gen_utils.h" #include #include @@ -24,16 +25,21 @@ using std::unordered_map; #endif // CLOCK_MONOTONIC #ifdef DEBUG -static unsigned long long pretime=0; +__thread unsigned long long pretime=0; static pthread_mutex_t debug_mutex; +static pthread_rwlock_t filters_rwlock; +static SQLite3DB * debugdb_disk = NULL; +sqlite3_stmt *statement1=NULL; +static unsigned int debug_output = 1; #endif /* DEBUG */ +/* static inline unsigned long long debug_monotonic_time() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (((unsigned long long) ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000); } - +*/ #define DEBUG_MSG_MAXSIZE 1024 @@ -48,7 +54,8 @@ static inline unsigned long long debug_monotonic_time() { std::set debug_filters; static bool filter_debug_entry(const char *__file, int __line, const char *__func) { - pthread_mutex_lock(&debug_mutex); + //pthread_mutex_lock(&debug_mutex); + pthread_rwlock_rdlock(&filters_rwlock); bool to_filter = false; if (debug_filters.size()) { // if the set is empty we aren't performing any filter, so we won't search std::string key(__file); @@ -88,46 +95,63 @@ static bool filter_debug_entry(const char *__file, int __line, const char *__fun } } } - pthread_mutex_unlock(&debug_mutex); + //pthread_mutex_unlock(&debug_mutex); + pthread_rwlock_unlock(&filters_rwlock); return to_filter; } // we use this function to sent the filters to Admin -// we hold here the mutex on debug_mutex +// we hold here the lock on filters_rwlock void proxy_debug_get_filters(std::set& f) { - pthread_mutex_lock(&debug_mutex); + //pthread_mutex_lock(&debug_mutex); + pthread_rwlock_rdlock(&filters_rwlock); f = debug_filters; - pthread_mutex_unlock(&debug_mutex); + pthread_rwlock_unlock(&filters_rwlock); + //pthread_mutex_unlock(&debug_mutex); } // we use this function to get the filters from Admin -// we hold here the mutex on debug_mutex +// we hold here the lock on filters_rwlock void proxy_debug_load_filters(std::set& f) { - pthread_mutex_lock(&debug_mutex); + //pthread_mutex_lock(&debug_mutex); + pthread_rwlock_wrlock(&filters_rwlock); debug_filters.erase(debug_filters.begin(), debug_filters.end()); debug_filters = f; - pthread_mutex_unlock(&debug_mutex); + pthread_rwlock_unlock(&filters_rwlock); + //pthread_mutex_unlock(&debug_mutex); } void proxy_debug_func(enum debug_module module, int verbosity, int thr, const char *__file, int __line, const char *__func, const char *fmt, ...) { assert(module=10) { @@ -150,18 +174,64 @@ void proxy_debug_func(enum debug_module module, int verbosity, int thr, const ch realname=abi::__cxa_demangle(debugbuff, 0, 0, &status); if (realname) { sprintf(debugbuff," ---- %s : %s\n", strings[i], realname); - strcat(longdebugbuff,debugbuff); + strcat(longdebugbuff2,debugbuff); } } //printf("\n"); - strcat(longdebugbuff,"\n"); + //strcat(longdebugbuff2,"\n"); free(strings); // } else { // fprintf(stderr, "%s", longdebugbuff); } #endif - if (strlen(longdebugbuff)) fprintf(stderr, "%s", longdebugbuff); + pthread_mutex_lock(&debug_mutex); + if (debugdb_disk == NULL) { + // default behavior + if (longdebugbuff[0] != 0) { + fprintf(stderr, "%s", longdebugbuff); + } + if (longdebugbuff2[0] != 0) { + if (GloVars.global.gdbg_lvl[module].verbosity>=10) { + fprintf(stderr, "%s\n", longdebugbuff2); + } + } + } else { + SQLite3DB *db = debugdb_disk; + int rc = 0; + if (statement1==NULL) { + const char *a = "INSERT INTO debug_log (id, time, lapse, thread, file, line, funct, modnum, modname, verbosity, message, note, backtrace) VALUES (NULL, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL, ?11)"; + rc=db->prepare_v2(a, &statement1); + ASSERT_SQLITE_OK(rc, db); + } + if (debug_output == 1 || debug_output == 3) { + // to stderr + if (longdebugbuff[0] != 0) { + fprintf(stderr, "%s", longdebugbuff); + } + if (longdebugbuff2[0] != 0) { + fprintf(stderr, "%s", longdebugbuff2); + } + } + if (write_to_disk == true) { + rc=(*proxy_sqlite3_bind_int64)(statement1, 1, curtime); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_int64)(statement1, 2, curtime-pretime); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_int64)(statement1, 3, thr); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_text)(statement1, 4, __file, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_int64)(statement1, 5, __line); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_text)(statement1, 6, __func, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_int64)(statement1, 7, module); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_text)(statement1, 8, GloVars.global.gdbg_lvl[module].name, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_int64)(statement1, 9, verbosity); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_text)(statement1, 10, origdebugbuff, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_bind_text)(statement1, 11, longdebugbuff2, -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, db); + SAFE_SQLITE3_STEP2(statement1); + rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, db); + rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, db); + } + } pthread_mutex_unlock(&debug_mutex); + if (curtime != 0) + pretime=curtime; }; #endif @@ -376,7 +446,7 @@ void print_backtrace(void) void init_debug_struct() { int i; pthread_mutex_init(&debug_mutex,NULL); - pretime=debug_monotonic_time(); + pthread_rwlock_init(&filters_rwlock, NULL); GloVars.global.gdbg_lvl= (debug_level *) malloc(PROXY_DEBUG_UNKNOWN*sizeof(debug_level)); for (i=0;iptr+sizeof(mysql_hdr)+1,query_length-1); query[query_length-1]=0; -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) if (sess->client_myds->proxy_addr.addr == NULL) { struct sockaddr addr; socklen_t addr_len=sizeof(struct sockaddr); @@ -363,7 +363,7 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p sess->client_myds->proxy_addr.addr = strdup("unknown"); } } -#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY +#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY || TEST_REPLICATIONLAG char *query_no_space=(char *)l_alloc(query_length); memcpy(query_no_space,query,query_length); @@ -534,13 +534,13 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p if (query_no_space_length==SELECT_VERSION_COMMENT_LEN) { if (!strncasecmp(SELECT_VERSION_COMMENT, query_no_space, query_no_space_length)) { l_free(query_length,query); -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) char *a = (char *)"SELECT '(ProxySQL Automated Test Server) - %s'"; query = (char *)malloc(strlen(a)+strlen(sess->client_myds->proxy_addr.addr)); sprintf(query,a,sess->client_myds->proxy_addr.addr); #else query=l_strdup("SELECT '(ProxySQL SQLite3 Server)'"); -#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY +#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY || TEST_REPLICATIONLAG query_length=strlen(query)+1; goto __run_query; } @@ -730,7 +730,7 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p __run_query: if (run_query) { -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) if (strncasecmp("SELECT",query_no_space,6)==0) { #ifdef TEST_AURORA if (strstr(query_no_space,(char *)"REPLICA_HOST_STATUS")) { @@ -802,6 +802,24 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p } } #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG + if (strncasecmp("SELECT SLAVE STATUS ", query_no_space, strlen("SELECT SLAVE STATUS ")) == 0) { + if (strlen(query_no_space) > strlen("SELECT SLAVE STATUS ") + 5) { + pthread_mutex_lock(&GloSQLite3Server->test_replicationlag_mutex); + // the current test doesn't try to simulate failures, therefore it will return immediately + if (GloSQLite3Server->replicationlag_map_size() == 0) { + // probably never initialized + GloSQLite3Server->load_replicationlag_table(sess); + } + const int rc = GloSQLite3Server->replicationlag_test_value(query_no_space + strlen("SELECT SLAVE STATUS ")); + free(query); + char* a = (char*)"SELECT %d as Seconds_Behind_Master"; + query = (char*)malloc(strlen(a) + 2); + sprintf(query, a, rc); + pthread_mutex_unlock(&GloSQLite3Server->test_replicationlag_mutex); + } + } +#endif // TEST_REPLICATIONLAG if (strstr(query_no_space,(char *)"Seconds_Behind_Master")) { free(query); char *a = (char *)"SELECT %d as Seconds_Behind_Master"; @@ -809,7 +827,7 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p sprintf(query,a,rand()%30+10); } } -#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY +#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY || TEST_REPLICATIONLAG SQLite3_Session *sqlite_sess = (SQLite3_Session *)sess->thread->gen_args; if (sess->autocommit==false) { sqlite3 *db = sqlite_sess->sessdb->get_db(); @@ -878,6 +896,16 @@ void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *p } } #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG + if (strncasecmp("SELECT", query_no_space, 6)) { + if (strstr(query_no_space, (char*)"REPLICATIONLAG_HOST_STATUS")) { + // the table is writable + pthread_mutex_lock(&GloSQLite3Server->test_replicationlag_mutex); + GloSQLite3Server->load_replicationlag_table(sess); + pthread_mutex_unlock(&GloSQLite3Server->test_replicationlag_mutex); + } + } +#endif // TEST_REPLICATIONLAG } l_free(pkt->size-sizeof(mysql_hdr),query_no_space); // it is always freed here l_free(query_length,query); @@ -1247,7 +1275,7 @@ SQLite3_Server::SQLite3_Server() { variables.read_only=false; -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) string s = ""; #ifdef TEST_AURORA @@ -1268,11 +1296,19 @@ SQLite3_Server::SQLite3_Server() { s += "0.0.0.0:3306"; pthread_mutex_init(&test_readonly_mutex, NULL); #endif //TEST_READONLY +#ifdef TEST_REPLICATIONLAG + // for replication test we listen on all IPs + if (!s.empty()) + s += ";"; + s += "0.0.0.0:3306"; + pthread_mutex_init(&test_replicationlag_mutex, NULL); +#endif //TEST_REPLICATIONLAG + variables.mysql_ifaces=strdup(s.c_str()); #else variables.mysql_ifaces=strdup("127.0.0.1:6030"); -#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY +#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY || TEST_REPLICATIONLAG }; @@ -1483,7 +1519,7 @@ void SQLite3_Server::populate_grouprep_table(MySQL_Session *sess, int txs_behind #endif // TEST_GALERA -#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#if defined(TEST_AURORA) || defined(TEST_GALERA) || defined(TEST_GROUPREP) || defined(TEST_READONLY) || defined(TEST_REPLICATIONLAG) void SQLite3_Server::insert_into_tables_defs(std::vector *tables_defs, const char *table_name, const char *table_def) { table_def_t *td = new table_def_t; td->table_name=strdup(table_name); @@ -1513,7 +1549,7 @@ void SQLite3_Server::drop_tables_defs(std::vector *tables_defs) { delete td; } }; -#endif // TEST_AURORA || TEST_GALERA || defined(TEST_GROUPREP) || defined(TEST_READONLY) +#endif // TEST_AURORA || TEST_GALERA || TEST_GROUPREP || TEST_READONLY || TEST_REPLICATIONLAG void SQLite3_Server::wrlock() { pthread_rwlock_wrlock(&rwlock); @@ -1567,7 +1603,18 @@ bool SQLite3_Server::init() { check_and_build_standard_tables(sessdb, tables_defs_readonly); GloAdmin->enable_readonly_testing(); #endif // TEST_READONLY +#ifdef TEST_REPLICATIONLAG + tables_defs_replicationlag = new std::vector; + insert_into_tables_defs(tables_defs_replicationlag, + (const char*)"REPLICATIONLAG_HOST_STATUS", + (const char*)"CREATE TABLE REPLICATIONLAG_HOST_STATUS (" + "hostname VARCHAR NOT NULL, port INT NOT NULL, seconds_behind_master INT NOT NULL, PRIMARY KEY (hostname, port)" + ")" + ); + check_and_build_standard_tables(sessdb, tables_defs_replicationlag); + GloAdmin->enable_replicationlag_testing(); +#endif // TEST_REPLICATIONLAG child_func[0]=child_mysql; main_shutdown=0; main_poll_nfds=0; @@ -1718,3 +1765,42 @@ int SQLite3_Server::readonly_test_value(char *p) { return rc; } #endif // TEST_READONLY + +#ifdef TEST_REPLICATIONLAG +void SQLite3_Server::load_replicationlag_table(MySQL_Session* sess) { + GloAdmin->mysql_servers_wrlock(); + replicationlag_map.clear(); + char* error = NULL; + int cols = 0; + int affected_rows = 0; + SQLite3_result* resultset = NULL; + sessdb->execute_statement((char*)"SELECT * FROM REPLICATIONLAG_HOST_STATUS", &error, &cols, &affected_rows, &resultset); + if (resultset) { + for (std::vector::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) { + SQLite3_row* r = *it; + const std::string& s = std::string(r->fields[0]) + ":" + std::string(r->fields[1]); + replicationlag_map[s] = atoi(r->fields[2]); + } + } + delete resultset; + if (replicationlag_map.size() == 0) { + GloAdmin->admindb->execute_statement((char*)"SELECT DISTINCT hostname, port FROM mysql_servers WHERE hostgroup_id BETWEEN 5202 AND 5700", &error, &cols, &affected_rows, &resultset); + for (std::vector::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) { + SQLite3_row* r = *it; + const std::string& s = "INSERT INTO REPLICATIONLAG_HOST_STATUS VALUES ('" + std::string(r->fields[0]) + "'," + std::string(r->fields[1]) + ",0)"; + sessdb->execute(s.c_str()); + } + delete resultset; + } + GloAdmin->mysql_servers_wrunlock(); +} + +int SQLite3_Server::replicationlag_test_value(const char* p) { + int rc = 0; // default + std::unordered_map::iterator it = replicationlag_map.find(std::string(p)); + if (it != replicationlag_map.end()) { + rc = it->second; + } + return rc; +} +#endif // TEST_REPLICATIONLAG diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index ec5be5fad2..c25554006c 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -812,10 +812,10 @@ int get_variable_value( res = EXIT_SUCCESS; } - mysql_free_result(admin_res); - cleanup: + mysql_free_result(admin_res); + return res; } @@ -1069,3 +1069,105 @@ int configure_endpoints( return EXIT_SUCCESS; } + +int extract_sqlite3_host_port(MYSQL* admin, std::pair& host_port) { + if (admin == nullptr) { return EINVAL; } + + const char varname[] { "sqliteserver-mysql_ifaces" }; + string sqlite3_ifaces {}; + + // ProxySQL is likely to have been launched without "--sqlite3-server" flag + if (get_variable_value(admin, varname, sqlite3_ifaces)) { + diag("ProxySQL was launched without '--sqlite3-server' flag"); + return EXIT_FAILURE; + } + + // Extract the correct port to connect to SQLite server + std::string::size_type colon_pos = sqlite3_ifaces.find(":"); + if (colon_pos == std::string::npos) { + diag("ProxySQL returned a malformed 'sqliteserver-mysql_ifaces': %s", sqlite3_ifaces.c_str()); + return EXIT_FAILURE; + } + + std::string sqlite3_host { sqlite3_ifaces.substr(0, colon_pos) }; + std::string sqlite3_port { sqlite3_ifaces.substr(colon_pos + 1) }; + + // Check that port has valid conversion + char* end_pos = nullptr; + int i_sqlite3_port = std::strtol(sqlite3_port.c_str(), &end_pos, 10); + + if (errno == ERANGE || (end_pos != &sqlite3_port.back() + 1)) { + diag("ProxySQL returned a invalid port number within 'sqliteserver-mysql_ifaces': %s", sqlite3_ifaces.c_str()); + return EXIT_FAILURE; + } + + host_port = { sqlite3_host, i_sqlite3_port }; + + return EXIT_SUCCESS; +} + +std::vector split(const std::string& s, char delim) { + std::istringstream tokenStream(s); + std::vector tokens {}; + + std::string token {}; + while (std::getline(tokenStream, token, delim)) { + tokens.push_back(token); + } + + return tokens; +} + +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; +} + +std::vector get_matching_lines(std::fstream& f_stream, const std::string& regex) { + std::vector 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(found_matches.back()) }; + const std::fstream::pos_type last_match_pos { std::get(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; +} diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index e06009e031..76b438758a 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -7,8 +7,9 @@ #include #include #include +#include -#include +#include "curl/curl.h" #include #include "command_line.h" @@ -441,4 +442,46 @@ int configure_endpoints( */ std::size_t count_matches(const std::string& str, const std::string& substr); +/** + * @brief Extracts the current 'sqliteserver-mysql_ifaces' from ProxySQL config. + * @param proxysql_admin An already opened connection to ProxySQL Admin. + * @param host_port Output param to hold the host and port of the current 'sqliteserver-mysql_ifaces'. + * @return EXIT_SUCCESS for success, EXIT_FAILURE otherwise. Error cause is logged. + */ +int extract_sqlite3_host_port(MYSQL* admin, std::pair& host_port); + +/** + * @brief Split the supplied string with the supplied delimiter. + * @param s The string to be split. + * @param delimiter The delimiter to use for splitting the string. + * @return String splits. + */ +std::vector split(const std::string& s, char delim); + +/** + * @brief Gets the supplied environmental variable as a std::string. + * @param var The variable to value to extract. + * @return The variable value if present, an empty string if not found. + */ +std::string get_env(const std::string& var); + +/** + * @brief Opens the file in the supplied path in the provided stream, and seeks the end of it. + * @param f_path Path to the file to open. + * @param f_logfile Output parameter with the stream to be updated with the oppened file. + * @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise. Error casuse is logged. + */ +int open_file_and_seek_end(const std::string& f_path, std::fstream& f_stream); + +using line_match_t = std::tuple; +enum LINE_MATCH_T { POS, LINE, MATCHES }; + +/** + * @brief Extracts the lines matching the regex from the supplied stream till reaching EOF. + * @param f_stream The stream to be matched with the regex. + * @param regex The regex used to match the stream line-by-line. + * @return All the lines found matching the regex. + */ +std::vector get_matching_lines(std::fstream& f_stream, const std::string& regex); + #endif // #define UTILS_H diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index c3e52a0394..322e2812ba 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -167,22 +167,22 @@ reg_test_3504-change_user_libmariadb_helper: reg_test_3504-change_user_helper.cp $(CXX) -DDEBUG reg_test_3504-change_user_helper.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl -std=c++11 -ltap $(STATIC_LIBS) -o reg_test_3504-change_user_libmariadb_helper -DGITVERSION=\"$(GIT_VERSION)\" reg_test_3504-change_user_libmysql_helper: reg_test_3504-change_user_helper.cpp - $(CXX) -DLIBMYSQL_HELPER -DDEBUG reg_test_3504-change_user_helper.cpp -I/usr/include/mysql -I$(IDIR) -I$(JSON_IDIR) -I../tap -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_3504-change_user_libmysql_helper -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DLIBMYSQL_HELPER -DDEBUG reg_test_3504-change_user_helper.cpp -I/usr/include/mysql -I$(CURL_IDIR) -I$(IDIR) -I$(JSON_IDIR) -I../tap -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_3504-change_user_libmysql_helper -DGITVERSION=\"$(GIT_VERSION)\" test_clickhouse_server_libmysql-t: test_clickhouse_server-t.cpp - $(CXX) -DLIBMYSQL_HELPER -DDEBUG test_clickhouse_server-t.cpp -I/usr/include/mysql -I$(IDIR) -I$(JSON_IDIR) -I../tap -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o test_clickhouse_server_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DLIBMYSQL_HELPER -DDEBUG test_clickhouse_server-t.cpp -I/usr/include/mysql -I$(CURL_IDIR) -I$(IDIR) -I$(JSON_IDIR) -I../tap -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o test_clickhouse_server_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" reg_test_stmt_resultset_err_no_rows_libmysql-t: reg_test_stmt_resultset_err_no_rows-t.cpp - $(CXX) -DLIBMYSQL_HELPER reg_test_stmt_resultset_err_no_rows-t.cpp -I/usr/include/mysql -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_stmt_resultset_err_no_rows_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DLIBMYSQL_HELPER reg_test_stmt_resultset_err_no_rows-t.cpp -I/usr/include/mysql -I$(CURL_IDIR) -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_stmt_resultset_err_no_rows_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" reg_test_mariadb_stmt_store_result_libmysql-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LIBDIR)/libtap.a - $(CXX) -DLIBMYSQL_HELPER reg_test_mariadb_stmt_store_result-t.cpp -I/usr/include/mysql -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_mariadb_stmt_store_result_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DLIBMYSQL_HELPER reg_test_mariadb_stmt_store_result-t.cpp -I/usr/include/mysql -I$(CURL_IDIR) -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o reg_test_mariadb_stmt_store_result_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" reg_test_mariadb_stmt_store_result_async-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LIBDIR)/libtap.a $(CXX) -DASYNC_API reg_test_mariadb_stmt_store_result-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl -std=c++11 -ltap $(STATIC_LIBS) -o reg_test_mariadb_stmt_store_result_async-t -DGITVERSION=\"$(GIT_VERSION)\" prepare_statement_err3024_libmysql-t: prepare_statement_err3024-t.cpp $(TAP_LIBDIR)/libtap.a - $(CXX) -DLIBMYSQL_HELPER prepare_statement_err3024-t.cpp -I/usr/include/mysql -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o prepare_statement_err3024_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DLIBMYSQL_HELPER prepare_statement_err3024-t.cpp -I/usr/include/mysql -I$(CURL_IDIR) -I$(IDIR) -I$(JSON_IDIR) -I../tap $(OPT) -L$(TAP_LIBDIR) -lpthread -ldl -std=c++11 -ltap -lmysqlclient -o prepare_statement_err3024_libmysql-t -DGITVERSION=\"$(GIT_VERSION)\" prepare_statement_err3024_async-t: prepare_statement_err3024-t.cpp $(TAP_LIBDIR)/libtap.a $(CXX) -DASYNC_API prepare_statement_err3024-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl -std=c++11 -ltap $(STATIC_LIBS) -o prepare_statement_err3024_async-t -DGITVERSION=\"$(GIT_VERSION)\" diff --git a/test/tap/tests/admin_show_fields_from-t.cpp b/test/tap/tests/admin_show_fields_from-t.cpp index 11d66d5d34..6ace382f26 100644 --- a/test/tap/tests/admin_show_fields_from-t.cpp +++ b/test/tap/tests/admin_show_fields_from-t.cpp @@ -54,6 +54,7 @@ int main() { while ((row = mysql_fetch_row(proxy_res))) { std::string table(row[0]); tables.push_back(table); + diag("Adding table: %s", row[0]); } mysql_free_result(proxy_res); mysql_close(proxysql_admin); @@ -76,10 +77,11 @@ int main() { char *query = (char *) malloc(strlen(queries[0]) + it->length() + 8); for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { sprintf(query,*it2, it->c_str()); + diag("Running query: %s", query); MYSQL_QUERY(proxysql_admin, query); MYSQL_RES* proxy_res = mysql_store_result(proxysql_admin); unsigned long rows = proxy_res->row_count; - ok(rows > 0 , "Number of rows in %s = %d", it->c_str(), rows); + ok(rows > 0 , "Number of rows in %s = %lu", it->c_str(), rows); mysql_free_result(proxy_res); } free(query); diff --git a/test/tap/tests/admin_show_table_status-t.cpp b/test/tap/tests/admin_show_table_status-t.cpp index ca191ac86b..42c02b7d07 100644 --- a/test/tap/tests/admin_show_table_status-t.cpp +++ b/test/tap/tests/admin_show_table_status-t.cpp @@ -54,6 +54,7 @@ int main() { while ((row = mysql_fetch_row(proxy_res))) { std::string table(row[0]); tables.push_back(table); + diag("Adding table: %s", row[0]); } mysql_free_result(proxy_res); mysql_close(proxysql_admin); @@ -76,6 +77,7 @@ int main() { char *query = (char *) malloc(strlen(queries[0]) + it->length() + 8); for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { sprintf(query,*it2, it->c_str()); + diag("Running query: %s", query); MYSQL_QUERY(proxysql_admin, query); MYSQL_RES* proxy_res = mysql_store_result(proxysql_admin); unsigned long rows = proxy_res->row_count; diff --git a/test/tap/tests/admin_various_commands3-t.cpp b/test/tap/tests/admin_various_commands3-t.cpp new file mode 100644 index 0000000000..4a80f16b08 --- /dev/null +++ b/test/tap/tests/admin_various_commands3-t.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::string; + +/* this test: + * enables mysql-have_ssl + * execute various command +*/ + +std::vector queries_t = { + "PROXYSQLTEST 22", + "PROXYSQLTEST 23", + "PROXYSQLTEST 24", + "PROXYSQLTEST 25", + "PROXYSQLTEST 26", + "PROXYSQLTEST 27", + "SELECT COUNT(*) FROM stats_mysql_query_digest" + }; + + +//std::vector vals = { 100, 345, 800, 999, 2037, 12345 }; +//std::vector vals = { 100, 345, 800, 999, 2037 }; +std::vector vals = { 100, 345, 800 }; + +std::vector queries = {}; + +int run_q(MYSQL *mysql, const char *q) { + MYSQL_QUERY(mysql,q); + return 0; +} +int main() { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + srandom(123); + + + for (auto it = vals.begin() ; it != vals.end() ; it++) { + std::string q = "PROXYSQLTEST 1 " + std::to_string(*it); + queries.push_back(q); + for (int i=0; i<5; i++) { + queries.push_back(queries_t[rand()%queries_t.size()]); + } + queries.push_back("SELECT COUNT(*) FROM stats_mysql_query_digest"); + for (int i=0; i<5; i++) { + queries.push_back(queries_t[rand()%queries_t.size()]); + } + if (rand()%2 == 0) { + queries.push_back("SELECT COUNT(*) FROM stats_mysql_query_digest_reset"); + } else { + queries.push_back("TRUNCATE TABLE stats.stats_mysql_query_digest"); + } + } + queries.push_back("TRUNCATE TABLE stats.stats_mysql_query_digest"); + + + + MYSQL* proxysql_admin = mysql_init(NULL); + // Initialize connections + if (!proxysql_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + + 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)); + return -1; + } + + MYSQL_QUERY(proxysql_admin, "SET mysql-have_ssl='true'"); + MYSQL_QUERY(proxysql_admin, "SET mysql-have_compress='true'"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + + + unsigned int p = queries.size(); + for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { + if ( + (strncasecmp(it2->c_str(), "SELECT ", 7)==0) + ) { + // extra test for each queries returning a resultset + p++; + } + } + plan(p); + diag("Running test with %lu queries", queries.size()); + + + for (std::vector::iterator it2 = queries.begin(); it2 != queries.end(); it2++) { + MYSQL* proxysql_admin = mysql_init(NULL); // local scope + if (!proxysql_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + mysql_ssl_set(proxysql_admin, NULL, NULL, NULL, NULL, NULL); + if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, CLIENT_SSL|CLIENT_COMPRESS)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + int rc = run_q(proxysql_admin, it2->c_str()); + ok(rc==0, "Query: %s" , it2->c_str()); + if ( + (strncasecmp(it2->c_str(), "SELECT ", 7)==0) + ) { + MYSQL_RES* proxy_res = mysql_store_result(proxysql_admin); + unsigned long long num_rows = mysql_num_rows(proxy_res); + ok(num_rows != 0 , "Returned rows: %llu" , num_rows); + mysql_free_result(proxy_res); + } + mysql_close(proxysql_admin); + } + mysql_close(proxysql_admin); + + return exit_status(); +} diff --git a/test/tap/tests/modules_server_test.h b/test/tap/tests/modules_server_test.h index dfd4e11ef5..e8facb8b52 100644 --- a/test/tap/tests/modules_server_test.h +++ b/test/tap/tests/modules_server_test.h @@ -37,6 +37,8 @@ int get_module_ifaces(MYSQL* proxysql_admin, const std::string varname, std::str cleanup: + mysql_free_result(admin_res); + return res; } diff --git a/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp b/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp index a56669236d..9d4624aeb5 100644 --- a/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp +++ b/test/tap/tests/reg_test_3625-sqlite3_session_client_error_limit-t.cpp @@ -26,96 +26,6 @@ using query_spec = std::tuple; const int sqlite3_port = 0; -/** - * @brief Extract the current 'sqliteserver-mysql_ifaces' from ProxySQL config. - * @param proxysql_admin An already opened connection to ProxySQL Admin. - * @return EXIT_SUCCESS, or one of the following error codes: - * - EINVAL if supplied 'proxysql_admin' is NULL. - * - '-1' in case of ProxySQL returns an 'NULL' row for the query selecting - * the variable 'sqliteserver-read_only'. - * - EXIT_FAILURE in case other operation failed. - */ -int get_sqlite3_ifaces(MYSQL* proxysql_admin, std::string& sqlite3_ifaces) { - if (proxysql_admin == NULL) { - return EINVAL; - } - - int res = EXIT_FAILURE; - - MYSQL_QUERY( - proxysql_admin, - "SELECT * FROM global_variables WHERE Variable_name='sqliteserver-mysql_ifaces'" - ); - - MYSQL_RES* admin_res = mysql_store_result(proxysql_admin); - if (!admin_res) { - diag("'mysql_store_result' at line %d failed: %s", __LINE__, mysql_error(proxysql_admin)); - goto cleanup; - } - - { - MYSQL_ROW row = mysql_fetch_row(admin_res); - if (!row || row[0] == nullptr || row[1] == nullptr) { - diag("'mysql_fetch_row' at line %d returned 'NULL'", __LINE__); - res = -1; - goto cleanup; - } - - std::string _sqlite3_ifaces { row[1] }; - sqlite3_ifaces = _sqlite3_ifaces; - res = EXIT_SUCCESS; - } - -cleanup: - - return res; -} - -int extract_sqlite3_host_port(MYSQL* proxysql_admin, std::pair& host_port) { - if (proxysql_admin == nullptr) { return EINVAL; } - int res = EXIT_SUCCESS; - - std::string sqlite3_ifaces {}; - int ifaces_err = get_sqlite3_ifaces(proxysql_admin, sqlite3_ifaces); - - // ProxySQL is likely to have been launched without "--sqlite3-server" flag - if (ifaces_err == -1) { - diag("ProxySQL was launched without '--sqlite3-server' flag"); - res = EXIT_FAILURE; - return res; - } - - // Extract the correct port to connect to SQLite server - std::string::size_type colon_pos = sqlite3_ifaces.find(":"); - if (colon_pos == std::string::npos) { - diag("ProxySQL returned a malformed 'sqliteserver-mysql_ifaces': %s", sqlite3_ifaces.c_str()); - res = EXIT_FAILURE; - return res; - } - - std::string sqlite3_host { sqlite3_ifaces.substr(0, colon_pos) }; - std::string sqlite3_port { sqlite3_ifaces.substr(colon_pos + 1) }; - - // Check that port has valid conversion - char* end_pos = nullptr; - int i_sqlite3_port = std::strtol(sqlite3_port.c_str(), &end_pos, 10); - - if (errno == ERANGE || (end_pos != &sqlite3_port.back() + 1)) { - diag( - "ProxySQL returned a invalid port number within 'sqliteserver-mysql_ifaces': %s", - sqlite3_ifaces.c_str() - ); - res = EXIT_FAILURE; - return res; - } - - if (res == EXIT_SUCCESS) { - host_port = { sqlite3_host, i_sqlite3_port }; - } - - return res; -} - int main(int argc, char** argv) { CommandLine cl; diff --git a/test/tap/tests/reg_test_3838-restapi_eintr-t.cpp b/test/tap/tests/reg_test_3838-restapi_eintr-t.cpp index c4d840dbbc..cd081cd0b1 100644 --- a/test/tap/tests/reg_test_3838-restapi_eintr-t.cpp +++ b/test/tap/tests/reg_test_3838-restapi_eintr-t.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include "curl/curl.h" #include #include diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp index 308581526f..e5897061ec 100644 --- a/test/tap/tests/test_cluster_sync-t.cpp +++ b/test/tap/tests/test_cluster_sync-t.cpp @@ -31,14 +31,18 @@ #include #include #include +#include +#include #include -#include #include #include #include #include #include +#include +#include +#include #include @@ -62,6 +66,8 @@ using std::string; using std::vector; using std::tuple; +using std::fstream; +using std::function; /** * @brief Helper function to verify that the sync of a table (or variable) have been performed. @@ -391,6 +397,505 @@ int check_mysql_servers_sync( return EXIT_SUCCESS; } +struct sync_payload_t { + function update_module_val; + string module; + string sync_variable; +}; + +int64_t fetch_single_int_res(MYSQL* admin) { + MYSQL_RES* myres = mysql_store_result(admin); + MYSQL_ROW myrow = mysql_fetch_row(myres); + + int64_t val = -1; + + if (myrow && myrow[0]) { + char* endptr = NULL; + val = std::strtol(myrow[0], &endptr, 10); + + if (myrow[0] == endptr) { + val = -1; + } + } + + mysql_free_result(myres); + + return val; +} + +int update_variable_val(const CommandLine& cl, MYSQL* admin, const string& type, const string& var_name) { + cfmt_t select_query { + cstr_format("SELECT variable_value FROM global_variables WHERE variable_name='%s'", var_name.c_str()) + }; + + MYSQL_QUERY_T(admin, select_query.str.c_str()); + int64_t cur_val = fetch_single_int_res(admin); + if (cur_val == -1) { + return EXIT_FAILURE; + } + + cfmt_t update_query { cstr_format("SET %s=%ld", var_name.c_str(), cur_val + 1) }; + MYSQL_QUERY_T(admin, update_query.str.c_str()); + + if (type == "admin") { + MYSQL_QUERY_T(admin, "LOAD ADMIN VARIABLES TO RUNTIME"); + } else if (type == "mysql") { + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + } + + return EXIT_SUCCESS; +} + +int update_mysql_servers(const CommandLine& cl, MYSQL* admin) { + const char select_max_conns_t[] { + "SELECT max_connections FROM mysql_servers WHERE hostgroup_id=" + "(SELECT default_hostgroup FROM mysql_users WHERE username='%s')" + }; + const char update_max_conns_t[] { + "UPDATE mysql_servers SET max_connections=%ld WHERE hostgroup_id=" + "(SELECT default_hostgroup FROM mysql_users WHERE username='%s')" + }; + + cfmt_t select_max_conns { cstr_format(select_max_conns_t, cl.username) }; + MYSQL_QUERY_T(admin, select_max_conns.str.c_str()); + int64_t cur_val = fetch_single_int_res(admin); + if (cur_val == -1) { + return EXIT_FAILURE; + } + + cfmt_t update_query { cstr_format(update_max_conns_t, cur_val + 1, cl.username) }; + MYSQL_QUERY_T(admin, update_query.str.c_str()); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + return EXIT_SUCCESS; +} + +int update_mysql_query_rules(const CommandLine& cl, MYSQL* admin) { + const char update_mysql_query_rules[] { + "INSERT INTO mysql_query_rules (active) VALUES (1)" + }; + + MYSQL_QUERY_T(admin, update_mysql_query_rules); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + + return EXIT_SUCCESS; +} + +/** + * @brief Assumes that 'proxysql_servers' holds at least the one entry required for this test. + * @details It's assumed that primary ProxySQL is part of a Cluster. + */ +int update_proxysql_servers(const CommandLine& cl, MYSQL* admin) { + const char update_proxysql_servers_t[] { + "UPDATE proxysql_servers SET comment='%s' WHERE hostname='%s' and port=%d" + }; + + cfmt_t update_servers { + cstr_format(update_proxysql_servers_t, std::to_string(time(NULL)).c_str(), cl.host, cl.admin_port) + }; + MYSQL_QUERY_T(admin, update_servers.str.c_str()); + MYSQL_QUERY_T(admin, "LOAD PROXYSQL SERVERS TO RUNTIME"); + + return EXIT_SUCCESS; +} + +const vector module_sync_payloads { + { + update_mysql_servers, + "mysql_servers", + "admin-cluster_mysql_servers_diffs_before_sync", + }, + { + update_mysql_query_rules, + "mysql_query_rules", + "admin-cluster_mysql_query_rules_diffs_before_sync", + }, + { + update_proxysql_servers, + "proxysql_servers", + "admin-cluster_proxysql_servers_diffs_before_sync", + }, + { + [] (const CommandLine& cl, MYSQL* admin) -> int { + return update_variable_val(cl, admin, "mysql", "mysql-ping_timeout_server"); + }, + "mysql_variables", + "admin-cluster_mysql_variables_diffs_before_sync" + }, + { + [] (const CommandLine& cl, MYSQL* admin) -> int { + return update_variable_val(cl, admin, "admin", "admin-refresh_interval"); + }, + "admin_variables", + "admin-cluster_admin_variables_diffs_before_sync" + }, + // TODO: LDAP pluging currently not loaded for this test + // { + // update_ldap_variables, + // "proxysql_servers", + // "admin-cluster_proxysql_servers_diffs_before_sync", + // }, +}; + +int wait_for_node_sync(MYSQL* admin, const vector queries, uint32_t timeout) { + uint waited = 0; + bool not_synced = false; + std::string failed_query {}; + + while (waited < timeout) { + not_synced = false; + + // Check that all the entries have been synced + for (const auto& query : queries) { + if (mysql_query(admin, query.c_str())) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return -1; + } + + MYSQL_RES* myres = mysql_store_result(admin); + MYSQL_ROW myrow = mysql_fetch_row(myres); + int row_value = -1; + + if (myrow && myrow[0]) { + row_value = std::atoi(myrow[0]); + } + + mysql_free_result(myres); + + if (row_value == 0) { + not_synced = true; + failed_query = query; + + diag("Not synced yet - Result: %d, Query: %s", row_value, query.c_str()); + break; + } + } + + if (not_synced) { + waited += 1; + sleep(1); + } else { + break; + } + } + + if (not_synced) { + diag("'wait_for_node_sync' timeout for query '%s'", failed_query.c_str()); + } + + return not_synced; +}; + +string fetch_remote_checksum(MYSQL* admin, const CommandLine& cl, const string& module) { + const char select_core_module_checksum_t[] { + "SELECT checksum FROM stats_proxysql_servers_checksums WHERE hostname='%s' AND port='%d' AND name='%s'" + }; + + cfmt_t select_checksum { cstr_format(select_core_module_checksum_t, cl.host, cl.admin_port, module.c_str()) }; + if (mysql_query(admin, select_checksum.str.c_str())) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return {}; + } + + string checksum {}; + + { + MYSQL_RES* myres = mysql_store_result(admin); + MYSQL_ROW myrow = mysql_fetch_row(myres); + + if (myrow && myrow[0]) { + checksum = myrow[0]; + } + + mysql_free_result(myres); + } + + return checksum; +}; + +string fetch_runtime_checksum(MYSQL* admin, const string& module) { + const char select_core_module_checksum_t[] { + "SELECT checksum FROM runtime_checksums_values WHERE name='%s'" + }; + + cfmt_t select_checksum { cstr_format(select_core_module_checksum_t, module.c_str()) }; + if (mysql_query(admin, select_checksum.str.c_str())) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return {}; + } + + string checksum {}; + + { + MYSQL_RES* myres = mysql_store_result(admin); + MYSQL_ROW myrow = mysql_fetch_row(myres); + + if (myrow && myrow[0]) { + checksum = myrow[0]; + } + + mysql_free_result(myres); + } + + return checksum; +}; + +int check_module_checksums_sync( + MYSQL* admin, MYSQL* r_admin, const CommandLine& cl, const sync_payload_t& module_sync, int diffs_sync +) { + const char new_remote_checksum_query_t[] { + "SELECT count(*) FROM stats_proxysql_servers_checksums WHERE " + "hostname='%s' AND port='%d' AND name='%s' AND checksum!='%s'" + }; + const char synced_runtime_checksums_query_t[] { + "SELECT COUNT(*) FROM runtime_checksums_values WHERE name='%s' AND checksum='%s'" + }; + + // Store current remote checksum value + const string& module { module_sync.module }; + + // Checksum can not be present if we have just added the remote + uint32_t CHECKSUM_SYNC_TIMEOUT = 3; + + const char wait_remote_checksums_init_t[] { + "SELECT LENGTH(checksum) FROM stats_proxysql_servers_checksums WHERE " + "hostname='%s' AND port='%d' AND name='%s'" + }; + cfmt_t wait_remote_checksums_init { + cstr_format(wait_remote_checksums_init_t, cl.host, cl.admin_port, module.c_str()) + }; + + int checksum_present = wait_for_node_sync( r_admin, { wait_remote_checksums_init.str }, CHECKSUM_SYNC_TIMEOUT); + if (checksum_present) { + diag("No checksum (or zero) detected int the target remote server for module '%s'", module.c_str()); + return EXIT_FAILURE; + } + + string cur_remote_checksum { fetch_remote_checksum(r_admin, cl, module) }; + if (cur_remote_checksum.empty()) { + diag("Failed to fetch current fetch for module '%s'", module.c_str()); + return EXIT_FAILURE; + } + + // Open the error log and fetch the final position + const string r_stderr { string(cl.workdir) + "test_cluster_sync_config/cluster_sync_node_stderr.txt" }; + fstream s_logfile {}; + + int of_err = open_file_and_seek_end(r_stderr, s_logfile); + if (of_err != EXIT_SUCCESS) { return of_err; } + + // Perform update operation + int upd_res = module_sync.update_module_val(cl, admin); + if (upd_res) { + diag("Failed to perform the update operation for module '%s'", module.c_str()); + return EXIT_FAILURE; + } + + // Wait for new checksum to be detected + cfmt_t new_remote_checksum_query { + cstr_format(new_remote_checksum_query_t, cl.host, cl.admin_port, module.c_str(), cur_remote_checksum.c_str()) + }; + int sync_res = wait_for_node_sync(r_admin, { new_remote_checksum_query.str }, CHECKSUM_SYNC_TIMEOUT); + + // Fetch the new remote checksum after the synchronization + string new_remote_checksum { fetch_remote_checksum(r_admin, cl, module) }; + if (new_remote_checksum.empty()) { + diag("Failed to fetch current fetch for module '%s'", module.c_str()); + return EXIT_FAILURE; + } + + ok( + sync_res == 0 && cur_remote_checksum != new_remote_checksum, + "New checksum SHOULD be DETECTED and SYNCED for module '%s' - old: '%s', new: '%s'", + module.c_str(), cur_remote_checksum.c_str(), new_remote_checksum.c_str() + ); + + // Get the current diff_check for the new detected checksum + cfmt_t select_diff_check { + cstr_format( + "SELECT diff_check FROM stats_proxysql_servers_checksums WHERE name='%s' AND hostname='%s' AND port=%d AND checksum='%s'", + module.c_str(), cl.host, cl.admin_port, new_remote_checksum.c_str() + ) + }; + MYSQL_QUERY_T(r_admin, select_diff_check.str.c_str()); + int64_t cur_diff_check = fetch_single_int_res(r_admin); + if (cur_diff_check == -1) { + diag("Failed to fetch current 'diff_check' for module '%s'", module.c_str()); + return EXIT_FAILURE; + } + + // We automatically fails this test if the checksum isn't even detected + if (sync_res == 0) { + MYSQL_QUERY_T(r_admin, "SELECT variable_value FROM global_variables WHERE variable_name='admin-cluster_check_interval_ms'"); + int64_t cluster_check_interval_ms = fetch_single_int_res(r_admin); + if (cluster_check_interval_ms == -1) { + diag("Failed to fetch 'cluster_check_interval_ms'"); + return EXIT_FAILURE; + } + + const double cluster_check_interval_s = static_cast(cluster_check_interval_ms) / 1000; + const int SYNC_TIMEOUT = 5; + + // Check that configuration was properly applied by checking 'runtime_checksums' for module + cfmt_t synced_runtime_checksums_query { + cstr_format(synced_runtime_checksums_query_t, module.c_str(), new_remote_checksum.c_str()) + }; + int sync_res = wait_for_node_sync(r_admin, { synced_runtime_checksums_query.str }, SYNC_TIMEOUT); + string runtime_checksum { fetch_runtime_checksum(r_admin, module.c_str()) }; + + if (diffs_sync) { + // Check that error log has a new two new entries matching the exp 'diff_checks' + const string diff_check_regex { + "Cluster: detected a peer .* with " + module + " version \\d+, epoch \\d+, diff_check \\d+." + }; + vector new_matching_lines { get_matching_lines(s_logfile, diff_check_regex) }; + diag("Regex used find loglines: `%s`", diff_check_regex.c_str()); + + for (const line_match_t& line_match : new_matching_lines) { + diag( + "Found matching logline - pos: %ld, line: `%s`", + static_cast(std::get(line_match)), + std::get(line_match).c_str() + ); + } + + ok( + diffs_sync - 1 == new_matching_lines.size(), + "Expected to find 'diff_checks minus one' loglines matching regex - diff_checks: %d, found_lines: %ld", + diffs_sync, new_matching_lines.size() + ); + + ok( + sync_res == 0 && new_remote_checksum == runtime_checksum, + "Config SHOULD be fetched and synced checksum MATCH runtime - detected: %s, runtime: %s", + new_remote_checksum.c_str(), runtime_checksum.c_str() + ); + } else { + const string no_syncing_regex { + "Cluster: detected a new checksum for " + module + " from peer .*:\\d+, version \\d+, epoch \\d+, checksum .*." + " Not syncing due to '" + module_sync.sync_variable + "=0'" + }; + + vector new_matching_lines { get_matching_lines(s_logfile, no_syncing_regex) }; + diag("Regex used find loglines: `%s`", no_syncing_regex.c_str()); + + for (const line_match_t& line_match : new_matching_lines) { + diag( + "Found matching logline - pos: %ld, line: `%s`", + static_cast(std::get(line_match)), + std::get(line_match).c_str() + ); + } + + ok( + new_matching_lines.size() == 1, + "Expected to find ONE logline matching regex - diff_checks: %d, found_lines: %ld", + diffs_sync, new_matching_lines.size() + ); + + ok( + sync_res == 1 && new_remote_checksum != runtime_checksum, + "Config SHOULDN'T be fetched and synced checksum DON'T MATCH runtime - detected: %s, runtime: %s", + new_remote_checksum.c_str(), runtime_checksum.c_str() + ); + + // Check that 'diff_check' increased + MYSQL_QUERY_T(r_admin, select_diff_check.str.c_str()); + int64_t new_diff_check = fetch_single_int_res(r_admin); + + ok( + (new_diff_check - cur_diff_check) >= (SYNC_TIMEOUT - 1) / cluster_check_interval_s, + "There needs to be at least a difference of '%lf' in diff_check - old: %ld, new: %ld", + (SYNC_TIMEOUT - 1) / cluster_check_interval_s, cur_diff_check, new_diff_check + ); + + diag("Enabling sync for module '%s'", module.c_str()); + + // TODO: Re-enable the module and check that sync takes place + MYSQL_QUERY_T(r_admin, string {"SET " + module_sync.sync_variable + "=" + std::to_string(3)}.c_str()); + MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); + + diag("Check that sync takes place with 'admin_variables' module exception (due to newer checksum)"); + + // Wait for sync to take place and fetch the new runtime_checksum + cfmt_t synced_runtime_checksums_query { + cstr_format(synced_runtime_checksums_query_t, module.c_str(), new_remote_checksum.c_str()) + }; + int sync_res = wait_for_node_sync(r_admin, { synced_runtime_checksums_query.str }, SYNC_TIMEOUT); + string runtime_checksum { fetch_runtime_checksum(r_admin, module.c_str()) }; + + if (module != "admin_variables") { + ok( + sync_res == 0 && new_remote_checksum == runtime_checksum, + "Config SHOULD be fetched and synced checksum MATCH runtime - detected: %s, runtime: %s", + new_remote_checksum.c_str(), runtime_checksum.c_str() + ); + } else { + ok( + sync_res == 1 && new_remote_checksum != runtime_checksum, + "Config SHOULDN'T be fetched and synced checksum DON'T MATCH runtime - detected: %s, runtime: %s", + new_remote_checksum.c_str(), runtime_checksum.c_str() + ); + } + } + } + + return EXIT_SUCCESS; +} + +int check_modules_checksums_sync(MYSQL* admin, MYSQL* r_admin, const CommandLine& cl) { + const int module_diffs_sync = 2; + + for (const sync_payload_t& sync_payload : module_sync_payloads) { + const string set_query { "SET " + sync_payload.sync_variable + "=" + std::to_string(module_diffs_sync) }; + MYSQL_QUERY_T(r_admin, set_query.c_str()); + } + MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); + + printf("\n"); + diag("Start test with sync Enabled for all modules"); + + for (const sync_payload_t& sync_payload : module_sync_payloads) { + int check_sync = check_module_checksums_sync(admin, r_admin, cl, sync_payload, module_diffs_sync); + if (check_sync) { + diag("Enabled sync test failed for module '%s'. Aborting further testing.", sync_payload.module.c_str()); + return EXIT_FAILURE; + } + } + + for (size_t dis_module = 0; dis_module < module_sync_payloads.size(); dis_module++) { + printf("\n"); + diag("Start test with sync Disabled for module '%s'", module_sync_payloads[dis_module].module.c_str()); + + for (const sync_payload_t& sync_payload : module_sync_payloads) { + const string set_query { "SET " + sync_payload.sync_variable + "=" + std::to_string(module_diffs_sync) }; + MYSQL_QUERY_T(r_admin, set_query.c_str()); + } + MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); + + const string& module_sync_var { module_sync_payloads[dis_module].sync_variable }; + MYSQL_QUERY_T(r_admin, string {"SET " + module_sync_var + "=0"}.c_str()); + MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); + + for (size_t j = 0; j < module_sync_payloads.size(); j++) { + const sync_payload_t& sync_payload = module_sync_payloads[j]; + const int diffs_sync = j == dis_module ? 0 : module_diffs_sync; + + int check_sync = check_module_checksums_sync(admin, r_admin, cl, sync_payload, diffs_sync); + if (check_sync) { + if (diffs_sync) { + diag("Enabled sync test failed for module '%s'. Aborting further testing.", sync_payload.module.c_str()); + } else { + diag("Disabled sync test failed for module '%s'. Aborting further testing.", sync_payload.module.c_str()); + } + return EXIT_FAILURE; + } + } + } + + return EXIT_SUCCESS; +} + int main(int, char**) { int res = 0; CommandLine cl; @@ -401,7 +906,15 @@ int main(int, char**) { return EXIT_FAILURE; } - plan(15); + const size_t num_payloads = module_sync_payloads.size(); + plan( + // Sync tests by values + 15 + + // All modules enabled sync checksum tests + num_payloads * 3 + + // Module with disabled sync checksum tests + (num_payloads + ((num_payloads-1) * 3)) * 5 + ); const std::string fmt_config_file = std::string(cl.workdir) + "test_cluster_sync_config/test_cluster_sync.cnf"; @@ -460,21 +973,23 @@ int main(int, char**) { // Launch proxysql with cluster config std::thread proxy_replica_th([&save_proxy_stderr, &cl] () { - const std::string cluster_sync_node_stderr { - std::string(cl.workdir) + "test_cluster_sync_config/cluster_sync_node_stderr.txt" - }; + const string replica_stderr { string(cl.workdir) + "test_cluster_sync_config/cluster_sync_node_stderr.txt" }; const std::string proxysql_db = std::string(cl.workdir) + "test_cluster_sync_config/proxysql.db"; const std::string stats_db = std::string(cl.workdir) + "test_cluster_sync_config/proxysql_stats.db"; const std::string fmt_config_file = std::string(cl.workdir) + "test_cluster_sync_config/test_cluster_sync.cnf"; std::string proxy_stdout {}; std::string proxy_stderr {}; - int exec_res = wexecvp( - std::string(cl.workdir) + "../../../src/proxysql", { "-f", "-M", "-c", fmt_config_file.c_str() }, {}, - proxy_stdout, proxy_stderr - ); + const string proxy_binary_path { string { cl.workdir } + "../../../src/proxysql" }; - ok(exec_res == 0, "proxysql cluster node should execute and shutdown nicely. 'wexecvp' result was: %d", exec_res); + const string proxy_command { + proxy_binary_path + " -f -M -c " + fmt_config_file + " > " + replica_stderr + " 2>&1" + }; + + diag("Launching replica ProxySQL via 'system' with command: `%s`", proxy_command.c_str()); + int exec_res = system(proxy_command.c_str()); + + ok(exec_res == 0, "proxysql cluster node should execute and shutdown nicely. 'system' result was: %d", exec_res); // In case of error place in log the reason if (exec_res || save_proxy_stderr.load()) { @@ -485,12 +1000,6 @@ int main(int, char**) { } } - // Always log child process output to file - std::ofstream error_log_file {}; - error_log_file.open(cluster_sync_node_stderr); - error_log_file << proxy_stderr; - error_log_file.close(); - remove(proxysql_db.c_str()); remove(stats_db.c_str()); }); @@ -1744,6 +2253,30 @@ int main(int, char**) { MYSQL_QUERY__(proxy_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); } + sleep(2); + + // NOTE: Recovering the DISK configuration shouldn't be required. But due to current limitations + // regarding monitor a server could be permanently moved from it's original hostgroup in user + // configuration (mysql_servers table). A scenario like this could for example be: + // - A server is moved by Monitoring actions during any of the previous sync tests for hostgroups + // tables, for example, placed in the OFFLINE_HOSTGROUP. + // - A later reconfiguration via 'read_only_action' rewrites Admin 'mysql_servers' table, making this + // server permanent in user config. + // - The following checks expects to find this server in a particular hostgroup. But config is + // permanently altered, and fail. + // The possibility of this scenario make the backup mechanism of the previous sections insufficient. So + // right now the safest option is recover DISK configuration. + MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS FROM DISK"); + MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + // Check sync disable via 'admin-cluster_*_sync' variables + { + int checksum_sync_res = check_modules_checksums_sync(proxy_admin, r_proxy_admin, cl); + if (checksum_sync_res != EXIT_SUCCESS) { + goto cleanup; + } + } + cleanup: // Teardown config diff --git a/test/tap/tests/test_debug_filters-t.cpp b/test/tap/tests/test_debug_filters-t.cpp index 03a9ba41d8..c3b17bc2df 100644 --- a/test/tap/tests/test_debug_filters-t.cpp +++ b/test/tap/tests/test_debug_filters-t.cpp @@ -55,30 +55,6 @@ int set_statement_query(const CommandLine& cl) { return EXIT_SUCCESS; } -string get_env(const string& var) { - string f_path {}; - - char* p_infra_datadir = std::getenv("REGULAR_INFRA_DATADIR"); - if (p_infra_datadir != nullptr) { - f_path = p_infra_datadir; - } - - return f_path; -} - -int open_file_and_seek_end(const string& f_path, fstream& f_proxysql_log) { - f_proxysql_log.open(f_path.c_str(), fstream::in | fstream::out); - - if (!f_proxysql_log.is_open() || !f_proxysql_log.good()) { - diag("Failed to open 'proxysql.log' file: { path: %s, error: %d }", f_path.c_str(), errno); - return EXIT_FAILURE; - } - - f_proxysql_log.seekg(0, std::ios::end); - - return EXIT_SUCCESS; -} - using ext_res_t = std::tuple; ext_res_t ext_debug_line(const string& f_path, const string& str_err_regex, const function& proxy_action) { @@ -162,6 +138,11 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } + diag("This test is now disabled because the plan is to have debugging always enabled"); + plan(1); + ok(1,"This test is now disabled because the plan is to have debugging always enabled"); + return exit_status(); + const auto create_conn_action = [&cl]() -> int { return create_and_close_proxy_conn(cl); }; const auto set_statement_action = [&cl]() -> int { return set_statement_query(cl); }; diff --git a/test/tap/tests/test_digest_umap_aux-t.cpp b/test/tap/tests/test_digest_umap_aux-t.cpp new file mode 100644 index 0000000000..4f9168b87e --- /dev/null +++ b/test/tap/tests/test_digest_umap_aux-t.cpp @@ -0,0 +1,291 @@ +/** + * @file test_digest_umap_aux-t.cpp + * @brief This tests that the auxiliary digest map is working correctly. + * @details This test sends dummy queries to ProxySQL while also sending + * queries to read table stats_mysql_query_digest. Then, it checks that the + * execution time of the dummy queries has no been afected by the execution + * time of the queries that read from table stats_mysql_query_digest. Finally, + * check that the data stored in stats_mysql_query_digest is correct. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proxysql_utils.h" +#include "command_line.h" +#include "utils.h" +#include "tap.h" + +using std::vector; +using std::string; + +CommandLine cl; +double slowest_query = 0.0; +double fastest_query = 0.0; +std::atomic_bool stop(false); + +vector DUMMY_QUERIES = { + "SELECT 1", + "SELECT 1 UNION SELECT 2 UNION SELECT 3", + "SELECT 1 UNION SELECT 2", +}; +int num_dummy_queries_executed = 0; + +struct digest_stats { + int hostgroup; + string schemaname; + string username; + string client_address; + string digest; + string digest_text; + int count_star; + int first_seen; + int last_seen; + int sum_time; + int min_time; + int max_time; + int sum_rows_affected; + int sum_rows_sent; +}; + +class timer { +public: + std::chrono::time_point lastTime; + timer() : lastTime(std::chrono::high_resolution_clock::now()) {} + inline double elapsed() { + std::chrono::time_point thisTime = std::chrono::high_resolution_clock::now(); + double deltaTime = std::chrono::duration(thisTime-lastTime).count(); + lastTime = thisTime; + return deltaTime; + } +}; + +vector get_digest_stats(MYSQL* proxy_admin) { + const char* get_digest_stats_query = + "SELECT * FROM stats_mysql_query_digest WHERE username='root' AND " + "digest_text IN ('SELECT ?', 'SELECT ? UNION SELECT ?', 'SELECT ? UNION SELECT ? UNION SELECT ?') " + "ORDER BY hostgroup, schemaname, username, client_address, digest"; + diag("Running: %s", get_digest_stats_query); + vector ds_vector; + + int err = mysql_query(proxy_admin, get_digest_stats_query); + if (err) { + diag("Failed to executed query `%s`. Error: `%s`", get_digest_stats_query, mysql_error(proxy_admin)); + return ds_vector; + } + + MYSQL_RES *res = NULL; + res = mysql_store_result(proxy_admin); + MYSQL_ROW row; + while (row = mysql_fetch_row(res)) { + digest_stats ds = {}; + ds.hostgroup = atoi(row[0]); + ds.schemaname = row[1]; + ds.username = row[2]; + ds.client_address = row[3]; + ds.digest = row[4]; + ds.digest_text = row[5]; + ds.count_star = atoi(row[6]); + ds.first_seen = atoi(row[7]); + ds.last_seen = atoi(row[8]); + ds.sum_time = atoi(row[9]); + ds.min_time = atoi(row[10]); + ds.max_time = atoi(row[11]); + ds.sum_rows_affected = atoi(row[12]); + ds.sum_rows_sent = atoi(row[13]); + ds_vector.push_back(ds); + } + mysql_free_result(res); + + return ds_vector; +} + +void run_dummy_queries() { + MYSQL* proxy_mysql = mysql_init(NULL); + + if (!mysql_real_connect(proxy_mysql, 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_mysql)); + slowest_query = -1.0; + return; + } + + vector execution_times = {}; + MYSQL_RES *res = NULL; + while (!stop) { + for (int i = 0; i < DUMMY_QUERIES.size(); i++) { + timer stopwatch; + int err = mysql_query(proxy_mysql, DUMMY_QUERIES[i]); + execution_times.push_back(stopwatch.elapsed()); + if (err) { + diag( + "Failed to executed query `%s`. Error: `%s`", + DUMMY_QUERIES[i], mysql_error(proxy_mysql) + ); + slowest_query = -1.0; + mysql_close(proxy_mysql); + return; + } + res = mysql_store_result(proxy_mysql); + mysql_free_result(res); + } + num_dummy_queries_executed++; + } + mysql_close(proxy_mysql); + + slowest_query = *std::max_element(execution_times.begin(), execution_times.end()); +} + +void run_stats_digest_query(MYSQL* proxy_admin) { + const char *count_digest_stats_query = "SELECT COUNT(*) FROM stats_mysql_query_digest"; + vector execution_times = {}; + const int num_queries = 3; + MYSQL_RES *res; + + for (int i; i < num_queries; i++) { + diag("Running: %s", count_digest_stats_query); + timer stopwatch; + int err = mysql_query(proxy_admin, count_digest_stats_query); + execution_times.push_back(stopwatch.elapsed()); + if (err) { + diag( + "Failed to executed query `%s`. Error: `%s`", + count_digest_stats_query, mysql_error(proxy_admin) + ); + fastest_query = -1.0; + return; + } + res = mysql_store_result(proxy_admin); + mysql_free_result(res); + } + + fastest_query = *std::min_element(execution_times.begin(), execution_times.end()); +} + +int main(int argc, char** argv) { + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan(1 + DUMMY_QUERIES.size() * 3); // always specify the number of tests that are going to be performed + + MYSQL *proxy_admin = mysql_init(NULL); + if (!mysql_real_connect(proxy_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(proxy_admin)); + return EXIT_FAILURE; + } + + vector admin_queries = { + "DELETE FROM mysql_query_rules", + "LOAD MYSQL QUERY RULES TO RUNTIME", + "PROXYSQLTEST 1 1000", + }; + for (const auto &query : admin_queries) { + diag("Running: %s", query); + MYSQL_QUERY(proxy_admin, query); + } + + MYSQL *proxy_mysql = mysql_init(NULL); + if (!mysql_real_connect(proxy_mysql, 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_mysql)); + mysql_close(proxy_admin); + return EXIT_FAILURE; + } + + MYSQL_RES *res = NULL; + for (const auto &query : DUMMY_QUERIES) { + diag("Running: %s", query); + MYSQL_QUERY(proxy_mysql, query); + res = mysql_store_result(proxy_mysql); + mysql_free_result(res); + } + mysql_close(proxy_mysql); + + vector ds_vector_before = get_digest_stats(proxy_admin); + + std::thread run_dummy_queries_thread(run_dummy_queries); + std::thread run_stats_digest_query_thread(run_stats_digest_query, proxy_admin); + + run_stats_digest_query_thread.join(); + if (fastest_query == -1.0) { + fprintf( + stderr, "File %s, line %d, Error: " + "thread run_stats_digest_query_thread finished with errors", __FILE__, __LINE__ + ); + mysql_close(proxy_admin); + return EXIT_FAILURE; + } + + stop = true; + run_dummy_queries_thread.join(); + if (slowest_query == -1.0) { + fprintf( + stderr, "File %s, line %d, Error: " + "thread run_dummy_queries_thread finished with errors", __FILE__, __LINE__ + ); + mysql_close(proxy_admin); + return EXIT_FAILURE; + } + + ok( + slowest_query < fastest_query, + "The slowest dummy query must be faster than the fastest digests stats query.\n" + " Slowest dummy query time: %f.\n" + " Fastest count digest stats query time: %f.", + slowest_query, fastest_query + ); + + vector ds_vector_after = get_digest_stats(proxy_admin); + for (int i = 0; i < DUMMY_QUERIES.size(); i++) { + ok( + ds_vector_before[i].hostgroup == ds_vector_after[i].hostgroup && + ds_vector_before[i].schemaname == ds_vector_after[i].schemaname && + ds_vector_before[i].username == ds_vector_after[i].username && + ds_vector_before[i].client_address == ds_vector_after[i].client_address && + ds_vector_before[i].digest == ds_vector_after[i].digest && + ds_vector_before[i].digest_text == ds_vector_after[i].digest_text && + ds_vector_before[i].first_seen - 1 <= ds_vector_after[i].first_seen && + ds_vector_after[i].first_seen <= ds_vector_before[i].first_seen + 1, + "Hostgroup, schemaname, username, client_address, digest, digest_test and first_seen " + "should be equal in both digest stats.\n" + " Hostgroup -> before:`%d` - after:`%d`.\n" + " Schemaname -> before:`%s` - after:`%s`.\n" + " Username -> before:`%s` - after:`%s`.\n" + " Client_address -> before:`%s` - after:`%s`.\n" + " Digests -> before:`%s` - after:`%s`.\n" + " Digests_text -> before:`%s` - after:`%s`.\n" + " First_seen -> before:`%d` - after:`%d`.", + ds_vector_before[i].hostgroup, ds_vector_after[i].hostgroup, + ds_vector_before[i].schemaname.c_str(), ds_vector_after[i].schemaname.c_str(), + ds_vector_before[i].username.c_str(), ds_vector_after[i].username.c_str(), + ds_vector_before[i].client_address.c_str(), ds_vector_after[i].client_address.c_str(), + ds_vector_before[i].digest.c_str(), ds_vector_after[i].digest.c_str(), + ds_vector_before[i].digest_text.c_str(), ds_vector_after[i].digest_text.c_str(), + ds_vector_before[i].first_seen, ds_vector_after[i].first_seen + ); + ok( + ds_vector_after[i].count_star - ds_vector_before[i].count_star == num_dummy_queries_executed, + "Query `%s` should be executed %d times. Act:'%d'", + ds_vector_after[i].digest_text.c_str(), num_dummy_queries_executed, + ds_vector_after[i].count_star - ds_vector_before[i].count_star + ); + ok( + ds_vector_before[i].last_seen < ds_vector_after[i].last_seen && + ds_vector_before[i].sum_time < ds_vector_after[i].sum_time, + "Last_seen and sum_time must have increased.\n" + " Last_seen -> before:`%d` - after:`%d`.\n" + " Sum_time -> before:`%d` - after:`%d`.", + ds_vector_before[i].last_seen, ds_vector_after[i].last_seen, + ds_vector_before[i].sum_time, ds_vector_after[i].sum_time + ); + } + + return exit_status(); +} diff --git a/test/tap/tests/test_mysqlsh-t.cpp b/test/tap/tests/test_mysqlsh-t.cpp index e8879f207b..bb1324398f 100644 --- a/test/tap/tests/test_mysqlsh-t.cpp +++ b/test/tap/tests/test_mysqlsh-t.cpp @@ -17,18 +17,6 @@ using std::string; -std::vector split(const std::string& s, char delimiter) -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} - int main(int argc, char** argv) { CommandLine cl; diff --git a/test/tap/tests/test_prometheus_metrics-t.cpp b/test/tap/tests/test_prometheus_metrics-t.cpp index 763153c100..e6198d5f9f 100644 --- a/test/tap/tests/test_prometheus_metrics-t.cpp +++ b/test/tap/tests/test_prometheus_metrics-t.cpp @@ -31,18 +31,6 @@ using std::pair; using std::string; using std::tuple; -std::vector split(const std::string& s, char delimiter) { - std::vector tokens {}; - std::string token {}; - std::istringstream tokenStream(s); - - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - - return tokens; -} - int mysql_query_d(MYSQL* mysql, const char* query) { diag("Query: Issuing query '%s' to ('%s':%d)", query, mysql->host, mysql->port); return mysql_query(mysql, query); diff --git a/test/tap/tests/test_simple_embedded_HTTP_server-t.cpp b/test/tap/tests/test_simple_embedded_HTTP_server-t.cpp index bd92e35145..86d9328bb1 100644 --- a/test/tap/tests/test_simple_embedded_HTTP_server-t.cpp +++ b/test/tap/tests/test_simple_embedded_HTTP_server-t.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include "curl/curl.h" #include #include diff --git a/test/tap/tests/test_sqlite3_server-t.cpp b/test/tap/tests/test_sqlite3_server-t.cpp index 79296649a4..d29910f4a7 100644 --- a/test/tap/tests/test_sqlite3_server-t.cpp +++ b/test/tap/tests/test_sqlite3_server-t.cpp @@ -20,15 +20,20 @@ * * NOTE: 'sqliteserver-read_only' is completely omitted from this test because * it's **currently unused**. + * + * NOTE: For manually checking that the test is resilient to port change collisions, the script + * 'test_sqlite3_server.sh' can be used. Check the file itself for a more detailed description. */ #include +#include #include #include #include #include #include #include +#include #include #include @@ -37,6 +42,11 @@ #include "command_line.h" #include "utils.h" +using std::fstream; +using std::string; +using std::pair; +using std::vector; + using query_spec = std::tuple; const int sqlite3_port = 0; @@ -225,6 +235,99 @@ std::vector sqlite_intf_queries { "LOAD SQLITESERVER VARIABLES TO RUNTIME" }; +int check_errorlog_for_addrinuse(MYSQL* admin, fstream& logfile) { + const string command_regex { ".*\\[INFO\\] Received LOAD SQLITESERVER VARIABLES (FROM DISK|TO RUNTIME) command" }; + std::vector cmd_lines { get_matching_lines(logfile, command_regex) }; + + // NOTE: Delay for poll_timeout for SQLite3_Server - harcoded 500ms + usleep(1000 * 1000); + + const string bind_err_regex { ".*\\[ERROR\\] bind\\(\\): Address already in use" }; + std::vector err_lines { get_matching_lines(logfile, bind_err_regex) }; + + if (cmd_lines.empty()) { + diag("ERROR: Commands 'LOAD SQLITESERVER' not logged as expected"); + return -1; + } + + if (err_lines.empty() == false) { + const string& fst_errline { std::get(err_lines.front()) }; + diag("Error line detected in logfile: `%s`", fst_errline.c_str()); + + return 1; + } else { + return 0; + } +} + +string connect_with_retries(MYSQL* sqlite3, const CommandLine& cl, const pair& host_port) { + uint32_t n = 0; + uint32_t retries = 10; + bool conn_success = false; + + const char* host { host_port.first.c_str() }; + const int port { host_port.second }; + string conn_err {}; + + diag("Attempting connection to new interface on (%s,%d)", host, port); + + while (n < retries) { + MYSQL* sqlite3 = mysql_init(NULL); + conn_err.clear(); + + if (!mysql_real_connect(sqlite3, host, cl.username, cl.password, NULL, port, NULL, 0)) { + conn_err = mysql_error(sqlite3); + } + mysql_close(sqlite3); + + if (conn_err.empty() == false) { + diag("Connection attempt '%d 'to the new interface failed with error `%s`. Retring...", n, conn_err.c_str()); + usleep(500 * 1000); + n += 1; + } else { + break; + } + } + + return conn_err; +} + +int enforce_sqlite_iface_change(MYSQL*admin, fstream& logfile, const uint32_t retries = 10) { + std::pair host_port {}; + if (extract_sqlite3_host_port(admin, host_port)) { + return -1; + } + + int logcheck_err = check_errorlog_for_addrinuse(admin, logfile); + if (logcheck_err == -1) { + return logcheck_err; + } + + uint32_t n = 0; + while (logcheck_err == 1 && n < retries) { + const string old_sqlite3_port { std::to_string(host_port.second) }; + const string new_sqlite3_port { std::to_string(host_port.second + 5) }; + + MYSQL_QUERY_T(admin, ("SET sqliteserver-mysql_ifaces='127.0.0.1:" + new_sqlite3_port + "'").c_str()); + MYSQL_QUERY_T(admin, "LOAD SQLITESERVER VARIABLES TO RUNTIME"); + + usleep(100 * 1000); + + MYSQL_QUERY_T(admin, ("SET sqliteserver-mysql_ifaces='127.0.0.1:" + old_sqlite3_port + "'").c_str()); + MYSQL_QUERY_T(admin, "LOAD SQLITESERVER VARIABLES TO RUNTIME"); + + logcheck_err = check_errorlog_for_addrinuse(admin, logfile); + + if (logcheck_err == EXIT_SUCCESS) { + break; + } else { + n += 1; + } + } + + return logcheck_err; +} + int main(int argc, char** argv) { CommandLine cl; @@ -232,7 +335,8 @@ int main(int argc, char** argv) { plan( 2 /* Fail to connect with wrong username and password */ + successful_queries.size() + unsuccessful_queries.size() + admin_queries.size() + sqlite_intf_queries.size() - + 1 /* Connect to new setup interface */ + + 2 /* Check port is properly taken by ProxySQL without error after each change */ + + 2 /* Connect to new/old interfaces when changed */ ); if (cl.getEnv()) { @@ -258,7 +362,7 @@ int main(int argc, char** argv) { { std::pair host_port {}; - int 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; @@ -334,22 +438,24 @@ int main(int argc, char** argv) { // Reinitialize MYSQL handle mysql_close(proxysql_sqlite3); - proxysql_sqlite3 = mysql_init(NULL); + + 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; + } // Change SQLite interface and connect to new port for (const auto& admin_query : sqlite_intf_queries) { int query_err = mysql_query(proxysql_admin, admin_query.c_str()); - ok( - query_err == 0, "Admin query '%s' should succeed. Line: %d, Err: '%s'", - admin_query.c_str(), __LINE__, mysql_error(proxysql_admin) - ); + ok(query_err == 0, "Query should be executed successfully '%s'", admin_query.c_str()); } - // NOTE: Wait for ProxySQL to reconfigure, changing SQLite3 interface. - // Trying to perform a connection immediately after changing the - // interface could lead to 'EADDRINUSE' in ProxySQL side. - // UPDATE: Timeout increased to '5' seconds to avoid previously described issue. - sleep(5); + int iface_err = enforce_sqlite_iface_change(proxysql_admin, errlog); + ok(iface_err == 0, "SQLite3 iface should change without error being reported."); // Connect to the new interface std::pair new_host_port {}; @@ -359,35 +465,42 @@ int main(int argc, char** argv) { goto cleanup; } - // Connect with invalid username - bool success_to_connect = true; - std::string new_intf_conn_err {}; - if ( - !mysql_real_connect( - proxysql_sqlite3, new_host_port.first.c_str(), cl.username, cl.password, - NULL, new_host_port.second, NULL, 0 - ) - ) { - new_intf_conn_err = mysql_error(proxysql_sqlite3); - success_to_connect = false; - } + std::string new_intf_conn_err { connect_with_retries(proxysql_sqlite3, cl, new_host_port) }; ok( - success_to_connect, - "A connection to the new selected interface should success, error was: '%s'", + new_intf_conn_err.empty() == true, + "A connection to the new interface should success, error was: '%s'", new_intf_conn_err.c_str() ); - mysql_close(proxysql_sqlite3); + // Seek current end-of-file + errlog.seekg(0, std::ios::end); // Perform the final Admin queries for (const auto& admin_query : admin_queries) { int query_err = mysql_query(proxysql_admin, admin_query.c_str()); - ok( - query_err == 0, "Admin query '%s' should succeed. Line: %d, Err: '%s'", - admin_query.c_str(), __LINE__, mysql_error(proxysql_admin) - ); + ok(query_err == 0, "Query should be executed successfully '%s'", admin_query.c_str()); } + + iface_err = enforce_sqlite_iface_change(proxysql_admin, errlog, 20); + ok(iface_err == 0, "SQLite3 iface should change without error being reported."); + + std::string old_intf_conn_err {}; + + // NOTE: If the interface change has failed after the previously specified retries, we assume the + // interface could be locked somehow by ProxySQL, and we avoid trying to stablish a connection that + // could stall the test. Instead we intentionally fail. + if (iface_err == 0) { + old_intf_conn_err = connect_with_retries(proxysql_sqlite3, cl, host_port); + } else { + old_intf_conn_err = "Interface failed to be changed. Skipping connection attempt..."; + } + + ok( + old_intf_conn_err.empty() == true, + "A connection to the old interface should success, error was: '%s'", + old_intf_conn_err.c_str() + ); } cleanup: diff --git a/test/tap/tests/test_sqlite3_server.sh b/test/tap/tests/test_sqlite3_server.sh new file mode 100755 index 0000000000..26f0e582d3 --- /dev/null +++ b/test/tap/tests/test_sqlite3_server.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# This script allows to manually check that 'test_sqlite3_server-t' is resillient to port change collisions +# for 'SQLite3' interface. It assumes that the port being used is the default '6030'. The script should be +# executed prior to the test. It should keep failing to bind the port with output: +# +# ``` +# exit_code: 1, stderr: `Error: Couldn't setup listening socket (err=-3)` +# ``` +# +# During the testing when ProxySQL changes the port, the script takes ownwership for 5 seconds. Triggering +# the section of the test of instructing to ProxySQL to reload SQLite3 interface due to the EADDRINUSE. + +while true; do + stderr=$(netcat -l -p 6030 -w 5 2>&1); + + if [[ $? == 1 ]] && [[ ${#stderr} == 0 ]]; then + break; + else + echo "exit_code: $?, stderr: \`$stderr\`"; + fi; + + sleep 0.1; +done