From 9055c8beb41a8a07451892aeadac6f83ecedf9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Mon, 28 Oct 2024 11:42:31 +0100 Subject: [PATCH 01/41] Refs #8141: Time Server Deploy - Initial research --- Time_research | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Time_research diff --git a/Time_research b/Time_research new file mode 100644 index 0000000..92081da --- /dev/null +++ b/Time_research @@ -0,0 +1,7 @@ +apt-get install chrony +vim /etc/chrony/chrony.conf +systemctl restart chrony +systemctl status chrony +date +la /var/lib/chrony/chrony.drift +cat /var/lib/chrony/chrony.drift From e96ace76e996b623e694321b57ae1c58814acd8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Mon, 28 Oct 2024 11:47:23 +0100 Subject: [PATCH 02/41] Refs #8141: Time Server Deploy - Delete research file --- Time_research | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Time_research diff --git a/Time_research b/Time_research deleted file mode 100644 index 92081da..0000000 --- a/Time_research +++ /dev/null @@ -1,7 +0,0 @@ -apt-get install chrony -vim /etc/chrony/chrony.conf -systemctl restart chrony -systemctl status chrony -date -la /var/lib/chrony/chrony.drift -cat /var/lib/chrony/chrony.drift From 7af67caaded27527d8b94196aabcf3e78b7a7054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Mon, 28 Oct 2024 15:51:58 +0100 Subject: [PATCH 03/41] Refs #8141: Time Server Deploy - Rol services structure --- playbooks/services.yml | 9 +++++++++ roles/services/defaults/main.yaml | 2 ++ roles/services/handlers/main.yml | 6 ++++++ roles/services/tasks/main.yml | 5 +++++ roles/services/templates/resolv.conf | 7 +++++++ 5 files changed, 29 insertions(+) create mode 100644 playbooks/services.yml create mode 100644 roles/services/defaults/main.yaml create mode 100644 roles/services/handlers/main.yml create mode 100644 roles/services/tasks/main.yml create mode 100644 roles/services/templates/resolv.conf diff --git a/playbooks/services.yml b/playbooks/services.yml new file mode 100644 index 0000000..0253201 --- /dev/null +++ b/playbooks/services.yml @@ -0,0 +1,9 @@ +- name: Configure Directory, Time, and Database Services + hosts: all + tasks: + - import_role: + name: ldap_samba + - import_role: + name: timeserver + - import_role: + name: database diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml new file mode 100644 index 0000000..3de00ea --- /dev/null +++ b/roles/services/defaults/main.yaml @@ -0,0 +1,2 @@ +variable1: false +variable2_checked: false diff --git a/roles/services/handlers/main.yml b/roles/services/handlers/main.yml new file mode 100644 index 0000000..38832ee --- /dev/null +++ b/roles/services/handlers/main.yml @@ -0,0 +1,6 @@ +- name: restart service + systemd: + name: service + state: restarted +- name: update example_command configuration + command: /usr/sbin/example_command diff --git a/roles/services/tasks/main.yml b/roles/services/tasks/main.yml new file mode 100644 index 0000000..ec88628 --- /dev/null +++ b/roles/services/tasks/main.yml @@ -0,0 +1,5 @@ +- import_tasks: witness.yml + tags: witness +- import_tasks: task.yml + tags: task + diff --git a/roles/services/templates/resolv.conf b/roles/services/templates/resolv.conf new file mode 100644 index 0000000..52a1891 --- /dev/null +++ b/roles/services/templates/resolv.conf @@ -0,0 +1,7 @@ +domain {{ host_domain }} +search {{ host_domain }} +{% if resolvers is defined %} +{% for resolver in resolvers %} +nameserver {{resolver}} +{% endfor %} +{% endif %} \ No newline at end of file From 8869dc4fbed8940c9f3be38da82d6b9f764f7183 Mon Sep 17 00:00:00 2001 From: xavi Date: Tue, 29 Oct 2024 12:16:30 +0100 Subject: [PATCH 04/41] test: Add test commit to delete --- delete_testigo_file | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 delete_testigo_file diff --git a/delete_testigo_file b/delete_testigo_file new file mode 100644 index 0000000..e69de29 From e214479a5aa8bd37fc571aeb304b833e1e51fda0 Mon Sep 17 00:00:00 2001 From: xavi Date: Tue, 29 Oct 2024 12:19:12 +0100 Subject: [PATCH 05/41] test: delete test file to commit --- delete_testigo_file | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 delete_testigo_file diff --git a/delete_testigo_file b/delete_testigo_file deleted file mode 100644 index e69de29..0000000 From 5b3996207bbb83b90ffbdd801acccf870fc0d51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Fri, 22 Nov 2024 10:15:34 +0100 Subject: [PATCH 06/41] Refs #8141: Time Server Deploy - Rol services structure inside services --- roles/services/tasks/main.yml | 7 +++---- roles/services/tasks/timeserver.yml | 2 ++ roles/services/templates/custom.conf | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 roles/services/tasks/timeserver.yml create mode 100644 roles/services/templates/custom.conf diff --git a/roles/services/tasks/main.yml b/roles/services/tasks/main.yml index ec88628..b4222aa 100644 --- a/roles/services/tasks/main.yml +++ b/roles/services/tasks/main.yml @@ -1,5 +1,4 @@ -- import_tasks: witness.yml - tags: witness -- import_tasks: task.yml - tags: task +- import_tasks: timeserver.yml + tags: timeserver + diff --git a/roles/services/tasks/timeserver.yml b/roles/services/tasks/timeserver.yml new file mode 100644 index 0000000..139597f --- /dev/null +++ b/roles/services/tasks/timeserver.yml @@ -0,0 +1,2 @@ + + diff --git a/roles/services/templates/custom.conf b/roles/services/templates/custom.conf new file mode 100644 index 0000000..8bcd532 --- /dev/null +++ b/roles/services/templates/custom.conf @@ -0,0 +1,17 @@ +# vendor zone +{{ timeserver_vendor_zone }} + +# Logging +log tracking statistics + +# will serve as local NTP server +local stratum 10 + +# restrict clients from your subnets +allow {{ timeserver_restrict_clients_zone }} + +# in case this server lost INTERNET connection +local stratum 10 + +# in case you wanna to broadcat time in your subnet +{{ timeserver_broadcat_subnet }} From 9df86be7a418aa6c1a6108d8b7215f9c881c7343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 26 Nov 2024 12:07:33 +0100 Subject: [PATCH 07/41] Refs #8141: Time Server Deploy - finish role --- roles/services/defaults/main.yaml | 4 ++-- roles/services/handlers/main.yml | 7 +++---- roles/services/tasks/main.yml | 2 -- roles/services/tasks/timeserver.yml | 13 +++++++++++++ roles/services/templates/resolv.conf | 7 ------- .../{custom.conf => timeserver_custom.conf} | 0 6 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 roles/services/templates/resolv.conf rename roles/services/templates/{custom.conf => timeserver_custom.conf} (100%) diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index 3de00ea..139597f 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -1,2 +1,2 @@ -variable1: false -variable2_checked: false + + diff --git a/roles/services/handlers/main.yml b/roles/services/handlers/main.yml index 38832ee..a1957d8 100644 --- a/roles/services/handlers/main.yml +++ b/roles/services/handlers/main.yml @@ -1,6 +1,5 @@ -- name: restart service +- name: restart-chrony systemd: - name: service + name: chrony state: restarted -- name: update example_command configuration - command: /usr/sbin/example_command + diff --git a/roles/services/tasks/main.yml b/roles/services/tasks/main.yml index b4222aa..c57d667 100644 --- a/roles/services/tasks/main.yml +++ b/roles/services/tasks/main.yml @@ -1,4 +1,2 @@ - import_tasks: timeserver.yml tags: timeserver - - diff --git a/roles/services/tasks/timeserver.yml b/roles/services/tasks/timeserver.yml index 139597f..21432d1 100644 --- a/roles/services/tasks/timeserver.yml +++ b/roles/services/tasks/timeserver.yml @@ -1,2 +1,15 @@ +- name: Install NRPE packages + apt: + name: chrony + state: present + install_recommends: no +- name: Set NRPE generic configuration + template: + src: timeserver_custom.conf + dest: /etc/chrony/conf.d/custom.conf + owner: root + group: root + mode: u=rw,g=r,o=r + notify: restart-chrony diff --git a/roles/services/templates/resolv.conf b/roles/services/templates/resolv.conf deleted file mode 100644 index 52a1891..0000000 --- a/roles/services/templates/resolv.conf +++ /dev/null @@ -1,7 +0,0 @@ -domain {{ host_domain }} -search {{ host_domain }} -{% if resolvers is defined %} -{% for resolver in resolvers %} -nameserver {{resolver}} -{% endfor %} -{% endif %} \ No newline at end of file diff --git a/roles/services/templates/custom.conf b/roles/services/templates/timeserver_custom.conf similarity index 100% rename from roles/services/templates/custom.conf rename to roles/services/templates/timeserver_custom.conf From 31180a8d66bed7ab3902a43a3efa85cda876356b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 26 Nov 2024 12:54:55 +0100 Subject: [PATCH 08/41] Refs #8141: Time Server Deploy - finish role --- playbooks/services.yml | 10 ++++------ roles/services/tasks/timeserver.yml | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/playbooks/services.yml b/playbooks/services.yml index 0253201..cf219b7 100644 --- a/playbooks/services.yml +++ b/playbooks/services.yml @@ -1,9 +1,7 @@ - name: Configure Directory, Time, and Database Services hosts: all tasks: - - import_role: - name: ldap_samba - - import_role: - name: timeserver - - import_role: - name: database + - name: Configure services to install in the server + import_role: + name: services + diff --git a/roles/services/tasks/timeserver.yml b/roles/services/tasks/timeserver.yml index 21432d1..5f3d850 100644 --- a/roles/services/tasks/timeserver.yml +++ b/roles/services/tasks/timeserver.yml @@ -1,9 +1,9 @@ -- name: Install NRPE packages +- name: Install CHRONY packages apt: name: chrony state: present install_recommends: no -- name: Set NRPE generic configuration +- name: Set CHRONY generic configuration template: src: timeserver_custom.conf dest: /etc/chrony/conf.d/custom.conf From 8c88bd8a8052ef1339de4dd36dc632657337083e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Thu, 28 Nov 2024 13:26:02 +0100 Subject: [PATCH 09/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/files/mariadb_override.conf | 3 + roles/services/files/scripts/README.md | 19 + roles/services/files/scripts/check-memory.sh | 9 + roles/services/files/scripts/export-privs.sh | 25 + roles/services/files/scripts/mysqltuner.pl | 7141 +++++++++++++++++ .../services/files/scripts/promote-master.sh | 15 + roles/services/files/scripts/promote-slave.sh | 16 + roles/services/files/scripts/scheduler-log.sh | 49 + roles/services/files/scripts/sync-conf.sh | 21 + roles/services/files/z90-vn.cnf | 120 + roles/services/files/z95-production.cnf | 6 + roles/services/files/z99-local.cnf | 7 + roles/services/handlers/main.yml | 3 + roles/services/tasks/main.yml | 2 + roles/services/tasks/mariadb.yml | 41 + 15 files changed, 7477 insertions(+) create mode 100644 roles/services/files/mariadb_override.conf create mode 100644 roles/services/files/scripts/README.md create mode 100644 roles/services/files/scripts/check-memory.sh create mode 100644 roles/services/files/scripts/export-privs.sh create mode 100644 roles/services/files/scripts/mysqltuner.pl create mode 100644 roles/services/files/scripts/promote-master.sh create mode 100644 roles/services/files/scripts/promote-slave.sh create mode 100644 roles/services/files/scripts/scheduler-log.sh create mode 100644 roles/services/files/scripts/sync-conf.sh create mode 100644 roles/services/files/z90-vn.cnf create mode 100644 roles/services/files/z95-production.cnf create mode 100644 roles/services/files/z99-local.cnf create mode 100644 roles/services/tasks/mariadb.yml diff --git a/roles/services/files/mariadb_override.conf b/roles/services/files/mariadb_override.conf new file mode 100644 index 0000000..678e16a --- /dev/null +++ b/roles/services/files/mariadb_override.conf @@ -0,0 +1,3 @@ +[Service] +LimitNOFILE=600000 +LimitMEMLOCK=2M diff --git a/roles/services/files/scripts/README.md b/roles/services/files/scripts/README.md new file mode 100644 index 0000000..1c5d401 --- /dev/null +++ b/roles/services/files/scripts/README.md @@ -0,0 +1,19 @@ +# Scripts to maintain MariaDB + +## scheduler-log.sh + +The following table should be created into MySQL/MariaDB database. + +``` +CREATE TABLE `eventLog` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `date` datetime NOT NULL, + `event` varchar(512) NOT NULL, + `error` varchar(1024) NOT NULL, + PRIMARY KEY (`id`), + KEY `date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Event scheduler error log' +``` + +Then adjust the *$logTable* variable to the correct schema. + diff --git a/roles/services/files/scripts/check-memory.sh b/roles/services/files/scripts/check-memory.sh new file mode 100644 index 0000000..cfb5938 --- /dev/null +++ b/roles/services/files/scripts/check-memory.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +minFree=5 +memFree=$(free --gibi | awk '$1 == "Mem:" { print $7 }') + +if [ "$memFree" -le "$minFree" ]; then + echo "Free memory is ${memFree}Gi, restarting mariadb service to prevent OOM killer..." + systemctl restart mariadb +fi diff --git a/roles/services/files/scripts/export-privs.sh b/roles/services/files/scripts/export-privs.sh new file mode 100644 index 0000000..e1f318d --- /dev/null +++ b/roles/services/files/scripts/export-privs.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +OUTFILE=privs.sql +SCHEMA=mysql +TABLES=( + global_priv + db + tables_priv + columns_priv + procs_priv + proxies_priv + roles_mapping +) + +echo "USE \`$SCHEMA\`;" > "$OUTFILE" + +for TABLE in "${TABLES[@]}" +do + echo "TRUNCATE TABLE \`$SCHEMA\`.\`$TABLE\`;" >> "$OUTFILE" +done + +echo "" >> "$OUTFILE" +mysqldump --no-create-info --skip-triggers "$SCHEMA" ${TABLES[@]} >> "$OUTFILE" + +echo "FLUSH PRIVILEGES;" >> "$OUTFILE" diff --git a/roles/services/files/scripts/mysqltuner.pl b/roles/services/files/scripts/mysqltuner.pl new file mode 100644 index 0000000..2b526d2 --- /dev/null +++ b/roles/services/files/scripts/mysqltuner.pl @@ -0,0 +1,7141 @@ +#!/usr/bin/env perl +# mysqltuner.pl - Version 1.9.9 +# High Performance MySQL Tuning Script +# Copyright (C) 2006-2022 Major Hayden - major@mhtx.net +# Copyright (C) 2006-2022 Jean-Marie Renouard - jmrenouard@gmail.com + +# For the latest updates, please visit http://mysqltuner.pl/ +# Git repository available at https://github.com/major/MySQLTuner-perl +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This project would not be possible without help from: +# Matthew Montgomery Paul Kehrer Dave Burgess +# Jonathan Hinds Mike Jackson Nils Breunese +# Shawn Ashlee Luuk Vosslamber Ville Skytta +# Trent Hornibrook Jason Gill Mark Imbriaco +# Greg Eden Aubin Galinotti Giovanni Bechis +# Bill Bradford Ryan Novosielski Michael Scheidell +# Blair Christensen Hans du Plooy Victor Trac +# Everett Barnes Tom Krouper Gary Barrueto +# Simon Greenaway Adam Stein Isart Montane +# Baptiste M. Cole Turner Major Hayden +# Joe Ashcraft Jean-Marie Renouard Christian Loos +# Julien Francoz Daniel Black +# +# Inspired by Matthew Montgomery's tuning-primer.sh script: +# http://www.day32.com/MySQL/ +# +package main; + +use 5.005; +use strict; +use warnings; + +use diagnostics; +use File::Spec; +use Getopt::Long; +use Pod::Usage; +use File::Basename; +use Cwd 'abs_path'; + +#use Data::Dumper; +#$Data::Dumper::Pair = " : "; + +# for which() +#use Env; + +# Set up a few variables for use in the script +my $tunerversion = "1.9.9"; +my ( @adjvars, @generalrec ); + +# Set defaults +my %opt = ( + "silent" => 0, + "nobad" => 0, + "nogood" => 0, + "noinfo" => 0, + "debug" => 0, + "nocolor" => ( !-t STDOUT ), + "color" => 0, + "forcemem" => 0, + "forceswap" => 0, + "host" => 0, + "socket" => 0, + "port" => 0, + "user" => 0, + "pass" => 0, + "password" => 0, + "ssl-ca" => 0, + "skipsize" => 0, + "checkversion" => 0, + "updateversion" => 0, + "buffers" => 0, + "passwordfile" => 0, + "bannedports" => '', + "maxportallowed" => 0, + "outputfile" => 0, + "noprocess" => 0, + "dbstat" => 0, + "nodbstat" => 0, + "server-log" => '', + "tbstat" => 0, + "notbstat" => 0, + "colstat" => 0, + "nocolstat" => 0, + "idxstat" => 0, + "noidxstat" => 0, + "sysstat" => 0, + "nosysstat" => 0, + "pfstat" => 0, + "nopfstat" => 0, + "skippassword" => 0, + "noask" => 0, + "template" => 0, + "json" => 0, + "prettyjson" => 0, + "reportfile" => 0, + "verbose" => 0, + "defaults-file" => '', + "protocol" => '', +); + +# Gather the options from the command line +GetOptions( + \%opt, 'nobad', + 'nogood', 'noinfo', + 'debug', 'nocolor', + 'forcemem=i', 'forceswap=i', + 'host=s', 'socket=s', + 'port=i', 'user=s', + 'pass=s', 'skipsize', + 'checkversion', 'mysqladmin=s', + 'mysqlcmd=s', 'help', + 'buffers', 'skippassword', + 'passwordfile=s', 'outputfile=s', + 'silent', 'noask', + 'json', 'prettyjson', + 'template=s', 'reportfile=s', + 'cvefile=s', 'bannedports=s', + 'updateversion', 'maxportallowed=s', + 'verbose', 'password=s', + 'passenv=s', 'userenv=s', + 'defaults-file=s', 'ssl-ca=s', + 'color', 'noprocess', + 'dbstat', 'nodbstat', + 'tbstat', 'notbstat', + 'colstat', 'nocolstat', + 'sysstat', 'nosysstat', + 'pfstat', 'nopfstat', + 'idxstat', 'noidxstat', + 'server-log=s', 'protocol=s', + ) + or pod2usage( + -exitval => 1, + -verbose => 99, + -sections => [ + "NAME", + "IMPORTANT USAGE GUIDELINES", + "CONNECTION AND AUTHENTICATION", + "PERFORMANCE AND REPORTING OPTIONS", + "OUTPUT OPTIONS" + ] + ); + +if ( defined $opt{'help'} && $opt{'help'} == 1 ) { + pod2usage( + -exitval => 0, + -verbose => 99, + -sections => [ + "NAME", + "IMPORTANT USAGE GUIDELINES", + "CONNECTION AND AUTHENTICATION", + "PERFORMANCE AND REPORTING OPTIONS", + "OUTPUT OPTIONS" + ] + ); +} + +my $devnull = File::Spec->devnull(); +my $basic_password_files = + ( $opt{passwordfile} eq "0" ) + ? abs_path( dirname(__FILE__) ) . "/basic_passwords.txt" + : abs_path( $opt{passwordfile} ); + +# Username from envvar +if ( exists $opt{userenv} && exists $ENV{ $opt{userenv} } ) { + $opt{user} = $ENV{ $opt{userenv} }; +} + +# Related to password option +if ( exists $opt{passenv} && exists $ENV{ $opt{passenv} } ) { + $opt{pass} = $ENV{ $opt{passenv} }; +} +$opt{pass} = $opt{password} if ( $opt{pass} eq 0 and $opt{password} ne 0 ); + +# for RPM distributions +$basic_password_files = "/usr/share/mysqltuner/basic_passwords.txt" + unless -f "$basic_password_files"; + +# check if we need to enable verbose mode +if ( $opt{verbose} ) { + $opt{checkversion} = 1; # Check for updates to MySQLTuner + $opt{dbstat} = 1; # Print database information + $opt{tbstat} = 1; # Print database information + $opt{idxstat} = 1; # Print index information + $opt{sysstat} = 1; # Print index information + $opt{buffers} = 1; # Print global and per-thread buffer values + $opt{pfstat} = 1; # Print performance schema info. + $opt{cvefile} = 'vulnerabilities.csv'; #CVE File for vulnerability checks +} +$opt{nocolor} = 1 if defined( $opt{outputfile} ); +$opt{tbstat} = 0 if ( $opt{notbstat} == 1 ); # Don't Print table information +$opt{colstat} = 0 if ( $opt{nocolstat} == 1 ); # Don't Print column information +$opt{dbstat} = 0 if ( $opt{nodbstat} == 1 ); # Don't Print database information +$opt{noprocess} = 0 + if ( $opt{noprocess} == 1 ); # Don't Print process information +$opt{sysstat} = 0 if ( $opt{nosysstat} == 1 ); # Don't Print sysstat information +$opt{pfstat} = 0 + if ( $opt{nopfstat} == 1 ); # Don't Print performance schema information +$opt{idxstat} = 0 if ( $opt{noidxstat} == 1 ); # Don't Print index information + +# for RPM distributions +$opt{cvefile} = "/usr/share/mysqltuner/vulnerabilities.csv" + unless ( defined $opt{cvefile} and -f "$opt{cvefile}" ); +$opt{cvefile} = '' unless -f "$opt{cvefile}"; +$opt{cvefile} = './vulnerabilities.csv' if -f './vulnerabilities.csv'; + +$opt{'bannedports'} = '' unless defined( $opt{'bannedports'} ); +my @banned_ports = split ',', $opt{'bannedports'}; + +# +my $outputfile = undef; +$outputfile = abs_path( $opt{outputfile} ) unless $opt{outputfile} eq "0"; + +my $fh = undef; +open( $fh, '>', $outputfile ) + or die("Fail opening $outputfile") + if defined($outputfile); +$opt{nocolor} = 1 if defined($outputfile); +$opt{nocolor} = 1 unless ( -t STDOUT ); + +$opt{nocolor} = 0 if ( $opt{color} == 1 ); + +# Setting up the colors for the print styles +my $me = `whoami`; +$me =~ s/\n//g; + +# Setting up the colors for the print styles +my $good = ( $opt{nocolor} == 0 ) ? "[\e[0;32mOK\e[0m]" : "[OK]"; +my $bad = ( $opt{nocolor} == 0 ) ? "[\e[0;31m!!\e[0m]" : "[!!]"; +my $info = ( $opt{nocolor} == 0 ) ? "[\e[0;34m--\e[0m]" : "[--]"; +my $deb = ( $opt{nocolor} == 0 ) ? "[\e[0;31mDG\e[0m]" : "[DG]"; +my $cmd = ( $opt{nocolor} == 0 ) ? "\e[1;32m[CMD]($me)" : "[CMD]($me)"; +my $end = ( $opt{nocolor} == 0 ) ? "\e[0m" : ""; + +# Maximum lines of log output to read from end +my $maxlines = 30000; + +# Checks for supported or EOL'ed MySQL versions +my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ); + +# Database +my @dblist; + +# Super structure containing all information +my %result; +$result{'MySQLTuner'}{'version'} = $tunerversion; +$result{'MySQLTuner'}{'datetime'} =`date '+%d-%m-%Y %H:%M:%S'`; +$result{'MySQLTuner'}{'options'} = \%opt; + +# Functions that handle the print styles +sub prettyprint { + print $_[0] . "\n" unless ( $opt{'silent'} or $opt{'json'} ); + print $fh $_[0] . "\n" if defined($fh); +} +sub goodprint { prettyprint $good. " " . $_[0] unless ( $opt{nogood} == 1 ); } +sub infoprint { prettyprint $info. " " . $_[0] unless ( $opt{noinfo} == 1 ); } +sub badprint { prettyprint $bad. " " . $_[0] unless ( $opt{nobad} == 1 ); } +sub debugprint { prettyprint $deb. " " . $_[0] unless ( $opt{debug} == 0 ); } + +sub redwrap { + return ( $opt{nocolor} == 0 ) ? "\e[0;31m" . $_[0] . "\e[0m" : $_[0]; +} + +sub greenwrap { + return ( $opt{nocolor} == 0 ) ? "\e[0;32m" . $_[0] . "\e[0m" : $_[0]; +} +sub cmdprint { prettyprint $cmd. " " . $_[0] . $end; } + +sub infoprintml { + for my $ln (@_) { $ln =~ s/\n//g; infoprint "\t$ln"; } +} + +sub infoprintcmd { + cmdprint "@_"; + infoprintml grep { $_ ne '' and $_ !~ /^\s*$/ } `@_ 2>&1`; +} + +sub subheaderprint { + my $tln = 100; + my $sln = 8; + my $ln = length("@_") + 2; + + prettyprint " "; + prettyprint "-" x $sln . " @_ " . "-" x ( $tln - $ln - $sln ); +} + +sub infoprinthcmd { + subheaderprint "$_[0]"; + infoprintcmd "$_[1]"; +} + +# Calculates the number of physical cores considering HyperThreading +sub cpu_cores { + if ( $^O eq 'linux' ) { + my $cntCPU = +`awk -F: '/^core id/ && !P[\$2] { CORES++; P[\$2]=1 }; /^physical id/ && !N[\$2] { CPUs++; N[\$2]=1 }; END { print CPUs*CORES }' /proc/cpuinfo`; + chomp $cntCPU; + return ( $cntCPU == 0 ? `nproc` : $cntCPU ); + } + + if ( $^O eq 'freebsd' ) { + my $cntCPU = `sysctl -n kern.smp.cores`; + chomp $cntCPU; + return $cntCPU + 0; + } + return 0; +} + +# Calculates the parameter passed in bytes, then rounds it to one decimal place +sub hr_bytes { + my $num = shift; + return "0B" unless defined($num); + return "0B" if $num eq "NULL"; + + if ( $num >= ( 1024**3 ) ) { #GB + return sprintf( "%.1f", ( $num / ( 1024**3 ) ) ) . "G"; + } + elsif ( $num >= ( 1024**2 ) ) { #MB + return sprintf( "%.1f", ( $num / ( 1024**2 ) ) ) . "M"; + } + elsif ( $num >= 1024 ) { #KB + return sprintf( "%.1f", ( $num / 1024 ) ) . "K"; + } + else { + return $num . "B"; + } +} + +sub hr_raw { + my $num = shift; + return "0" unless defined($num); + return "0" if $num eq "NULL"; + if ( $num =~ /^(\d+)G$/ ) { + return $1 * 1024 * 1024 * 1024; + } + if ( $num =~ /^(\d+)M$/ ) { + return $1 * 1024 * 1024; + } + if ( $num =~ /^(\d+)K$/ ) { + return $1 * 1024; + } + if ( $num =~ /^(\d+)$/ ) { + return $1; + } + return $num; +} + +# Calculates the parameter passed in bytes, then rounds it to the nearest integer +sub hr_bytes_rnd { + my $num = shift; + return "0B" unless defined($num); + return "0B" if $num eq "NULL"; + + if ( $num >= ( 1024**3 ) ) { #GB + return int( ( $num / ( 1024**3 ) ) ) . "G"; + } + elsif ( $num >= ( 1024**2 ) ) { #MB + return int( ( $num / ( 1024**2 ) ) ) . "M"; + } + elsif ( $num >= 1024 ) { #KB + return int( ( $num / 1024 ) ) . "K"; + } + else { + return $num . "B"; + } +} + +# Calculates the parameter passed to the nearest power of 1000, then rounds it to the nearest integer +sub hr_num { + my $num = shift; + if ( $num >= ( 1000**3 ) ) { # Billions + return int( ( $num / ( 1000**3 ) ) ) . "B"; + } + elsif ( $num >= ( 1000**2 ) ) { # Millions + return int( ( $num / ( 1000**2 ) ) ) . "M"; + } + elsif ( $num >= 1000 ) { # Thousands + return int( ( $num / 1000 ) ) . "K"; + } + else { + return $num; + } +} + +# Calculate Percentage +sub percentage { + my $value = shift; + my $total = shift; + $total = 0 unless defined $total; + $total = 0 if $total eq "NULL"; + return 100, 00 if $total == 0; + return sprintf( "%.2f", ( $value * 100 / $total ) ); +} + +# Calculates uptime to display in a more attractive form +sub pretty_uptime { + my $uptime = shift; + my $seconds = $uptime % 60; + my $minutes = int( ( $uptime % 3600 ) / 60 ); + my $hours = int( ( $uptime % 86400 ) / (3600) ); + my $days = int( $uptime / (86400) ); + my $uptimestring; + if ( $days > 0 ) { + $uptimestring = "${days}d ${hours}h ${minutes}m ${seconds}s"; + } + elsif ( $hours > 0 ) { + $uptimestring = "${hours}h ${minutes}m ${seconds}s"; + } + elsif ( $minutes > 0 ) { + $uptimestring = "${minutes}m ${seconds}s"; + } + else { + $uptimestring = "${seconds}s"; + } + return $uptimestring; +} + +# Retrieves the memory installed on this machine +my ( $physical_memory, $swap_memory, $duflags, $xargsflags ); + +sub memerror { + badprint +"Unable to determine total memory/swap; use '--forcemem' and '--forceswap'"; + exit 1; +} + +sub os_setup { + my $os = `uname`; + $duflags = ( $os =~ /Linux/ ) ? '-b' : ''; + $xargsflags = ( $os =~ /Darwin|SunOS/ ) ? '' : '-r'; + if ( $opt{'forcemem'} > 0 ) { + $physical_memory = $opt{'forcemem'} * 1048576; + infoprint "Assuming $opt{'forcemem'} MB of physical memory"; + if ( $opt{'forceswap'} > 0 ) { + $swap_memory = $opt{'forceswap'} * 1048576; + infoprint "Assuming $opt{'forceswap'} MB of swap space"; + } + else { + $swap_memory = 0; + badprint "Assuming 0 MB of swap space (use --forceswap to specify)"; + } + } + else { + if ( $os =~ /Linux|CYGWIN/ ) { + $physical_memory = + `grep -i memtotal: /proc/meminfo | awk '{print \$2}'` + or memerror; + $physical_memory *= 1024; + + $swap_memory = + `grep -i swaptotal: /proc/meminfo | awk '{print \$2}'` + or memerror; + $swap_memory *= 1024; + } + elsif ( $os =~ /Darwin/ ) { + $physical_memory = `sysctl -n hw.memsize` or memerror; + $swap_memory = + `sysctl -n vm.swapusage | awk '{print \$3}' | sed 's/\..*\$//'` + or memerror; + } + elsif ( $os =~ /NetBSD|OpenBSD|FreeBSD/ ) { + $physical_memory = `sysctl -n hw.physmem` or memerror; + if ( $physical_memory < 0 ) { + $physical_memory = `sysctl -n hw.physmem64` or memerror; + } + $swap_memory = + `swapctl -l | grep '^/' | awk '{ s+= \$2 } END { print s }'` + or memerror; + } + elsif ( $os =~ /BSD/ ) { + $physical_memory = `sysctl -n hw.realmem` or memerror; + $swap_memory = + `swapinfo | grep '^/' | awk '{ s+= \$2 } END { print s }'`; + } + elsif ( $os =~ /SunOS/ ) { + $physical_memory = + `/usr/sbin/prtconf | grep Memory | cut -f 3 -d ' '` + or memerror; + chomp($physical_memory); + $physical_memory = $physical_memory * 1024 * 1024; + } + elsif ( $os =~ /AIX/ ) { + $physical_memory = + `lsattr -El sys0 | grep realmem | awk '{print \$2}'` + or memerror; + chomp($physical_memory); + $physical_memory = $physical_memory * 1024; + $swap_memory = `lsps -as | awk -F"(MB| +)" '/MB /{print \$2}'` + or memerror; + chomp($swap_memory); + $swap_memory = $swap_memory * 1024 * 1024; + } + elsif ( $os =~ /windows/i ) { + $physical_memory = +`wmic ComputerSystem get TotalPhysicalMemory | perl -ne "chomp; print if /[0-9]+/;"` + or memerror; + $swap_memory = +`wmic OS get FreeVirtualMemory | perl -ne "chomp; print if /[0-9]+/;"` + or memerror; + } + } + debugprint "Physical Memory: $physical_memory"; + debugprint "Swap Memory: $swap_memory"; + chomp($physical_memory); + chomp($swap_memory); + chomp($os); + $result{'OS'}{'OS Type'} = $os; + $result{'OS'}{'Physical Memory'}{'bytes'} = $physical_memory; + $result{'OS'}{'Physical Memory'}{'pretty'} = hr_bytes($physical_memory); + $result{'OS'}{'Swap Memory'}{'bytes'} = $swap_memory; + $result{'OS'}{'Swap Memory'}{'pretty'} = hr_bytes($swap_memory); + $result{'OS'}{'Other Processes'}{'bytes'} = get_other_process_memory(); + $result{'OS'}{'Other Processes'}{'pretty'} = + hr_bytes( get_other_process_memory() ); +} + +sub get_http_cli { + my $httpcli = which( "curl", $ENV{'PATH'} ); + chomp($httpcli); + if ($httpcli) { + return $httpcli; + } + + $httpcli = which( "wget", $ENV{'PATH'} ); + chomp($httpcli); + if ($httpcli) { + return $httpcli; + } + return ""; +} + +# Checks for updates to MySQLTuner +sub validate_tuner_version { + if ( $opt{'checkversion'} eq 0 ) { + print "\n" unless ( $opt{'silent'} or $opt{'json'} ); + infoprint "Skipped version check for MySQLTuner script"; + return; + } + + my $update; + my $url = +"https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl"; + my $httpcli = get_http_cli(); + if ( $httpcli =~ /curl$/ ) { + debugprint "$httpcli is available."; + + debugprint +"$httpcli -m 3 -silent '$url' 2>/dev/null | grep 'my \$tunerversion'| cut -d\\\" -f2"; + $update = +`$httpcli -m 3 -silent '$url' 2>/dev/null | grep 'my \$tunerversion'| cut -d\\\" -f2`; + chomp($update); + debugprint "VERSION: $update"; + + compare_tuner_version($update); + return; + } + + if ( $httpcli =~ /wget$/ ) { + debugprint "$httpcli is available."; + + debugprint +"$httpcli -e timestamping=off -t 1 -T 3 -O - '$url' 2>$devnull| grep 'my \$tunerversion'| cut -d\\\" -f2"; + $update = +`$httpcli -e timestamping=off -t 1 -T 3 -O - '$url' 2>$devnull| grep 'my \$tunerversion'| cut -d\\\" -f2`; + chomp($update); + compare_tuner_version($update); + return; + } + debugprint "curl and wget are not available."; + infoprint "Unable to check for the latest MySQLTuner version"; + infoprint +"Using --pass and --password option is insecure during MySQLTuner execution(Password disclosure)" + if ( defined( $opt{'pass'} ) ); +} + +# Checks for updates to MySQLTuner +sub update_tuner_version { + if ( $opt{'updateversion'} eq 0 ) { + badprint "Skipped version update for MySQLTuner script"; + print "\n" unless ( $opt{'silent'} or $opt{'json'} ); + return; + } + + my $update; + my $fullpath=""; + my $url = "https://raw.githubusercontent.com/major/MySQLTuner-perl/master/"; + my @scripts = + ( "mysqltuner.pl", "basic_passwords.txt", "vulnerabilities.csv" ); + my $totalScripts = scalar(@scripts); + my $receivedScripts = 0; + my $httpcli = get_http_cli(); + + foreach my $script (@scripts) { + + if ( $httpcli =~ /curl$/ ) { + debugprint "$httpcli is available."; + + $fullpath=dirname(__FILE__)."/".$script; + debugprint "FullPath: $fullpath"; + debugprint + "$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath"; + $update = + `$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath`; + chomp($update); + debugprint "$script updated: $update"; + + if ( -s $script eq 0 ) { + badprint "Couldn't update $script"; + } + else { + ++$receivedScripts; + debugprint "$script updated: $update"; + } + } + elsif ( $httpcli =~ /wget$/ ) { + + debugprint "$httpcli is available."; + + debugprint +"$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'"; + $update = +`$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'`; + chomp($update); + + if ( -s $script eq 0 ) { + badprint "Couldn't update $script"; + } + else { + ++$receivedScripts; + debugprint "$script updated: $update"; + } + } + else { + debugprint "curl and wget are not available."; + infoprint "Unable to check for the latest MySQLTuner version"; + } + + } + + if ( $receivedScripts eq $totalScripts ) { + goodprint "Successfully updated MySQLTuner script"; + } + else { + badprint "Couldn't update MySQLTuner script"; + } + infoprint "Stopping program: MySQLTuner has be updated."; + exit 0; +} + +sub compare_tuner_version { + my $remoteversion = shift; + debugprint "Remote data: $remoteversion"; + + #exit 0; + if ( $remoteversion ne $tunerversion ) { + badprint + "There is a new version of MySQLTuner available($remoteversion)"; + update_tuner_version(); + return; + } + goodprint "You have the latest version of MySQLTuner($tunerversion)"; + return; +} + +# Checks to see if a MySQL login is possible +my ( $mysqllogin, $doremote, $remotestring, $mysqlcmd, $mysqladmincmd ); + +my $osname = $^O; +if ( $osname eq 'MSWin32' ) { + eval { require Win32; } or last; + $osname = Win32::GetOSName(); + infoprint "* Windows OS($osname) is not fully supported.\n"; + + #exit 1; +} + +sub mysql_setup { + $doremote = 0; + $remotestring = ''; + if ( $opt{mysqladmin} ) { + $mysqladmincmd = $opt{mysqladmin}; + } + else { + $mysqladmincmd = which( "mysqladmin", $ENV{'PATH'} ); + if ( !-e $mysqladmincmd ) { + $mysqladmincmd = which( "mariadb-admin", $ENV{'PATH'} ); + } + } + chomp($mysqladmincmd); + if ( !-e $mysqladmincmd && $opt{mysqladmin} ) { + badprint "Unable to find the mysqladmin command you specified: " + . $mysqladmincmd . ""; + exit 1; + } + elsif ( !-e $mysqladmincmd ) { + badprint +"Couldn't find mysqladmin/mariadb-admin in your \$PATH. Is MySQL installed?"; + exit 1; + } + if ( $opt{mysqlcmd} ) { + $mysqlcmd = $opt{mysqlcmd}; + } + else { + $mysqlcmd = which( "mysql", $ENV{'PATH'} ); + if ( !-e $mysqlcmd ) { + $mysqlcmd = which( "mariadb", $ENV{'PATH'} ); + } + } + chomp($mysqlcmd); + if ( !-e $mysqlcmd && $opt{mysqlcmd} ) { + badprint "Unable to find the mysql command you specified: " + . $mysqlcmd . ""; + exit 1; + } + elsif ( !-e $mysqlcmd ) { + badprint + "Couldn't find mysql/mariadb in your \$PATH. Is MySQL installed?"; + exit 1; + } + $mysqlcmd =~ s/\n$//g; + my $mysqlclidefaults = `$mysqlcmd --print-defaults`; + debugprint "MySQL Client: $mysqlclidefaults"; + if ( $mysqlclidefaults =~ /auto-vertical-output/ ) { + badprint + "Avoid auto-vertical-output in configuration file(s) for MySQL like"; + exit 1; + } + + debugprint "MySQL Client: $mysqlcmd"; + + $opt{port} = ( $opt{port} eq 0 ) ? 3306 : $opt{port}; + + # Are we being asked to connect via a socket? + if ( $opt{socket} ne 0 ) { + $remotestring = " -S $opt{socket} -P $opt{port}"; + } + + if ( $opt{protocol} ne '' ){ + $remotestring = " --protocol=$opt{protocol}"; + } + + # Are we being asked to connect to a remote server? + if ( $opt{host} ne 0 ) { + chomp( $opt{host} ); + +# If we're doing a remote connection, but forcemem wasn't specified, we need to exit + if ( $opt{'forcemem'} eq 0 + && ( $opt{host} ne "127.0.0.1" ) + && ( $opt{host} ne "localhost" ) ) + { + badprint "The --forcemem option is required for remote connections"; + exit 1; + } + infoprint "Performing tests on $opt{host}:$opt{port}"; + $remotestring = " -h $opt{host} -P $opt{port}"; + if ( ( $opt{host} ne "127.0.0.1" ) && ( $opt{host} ne "localhost" ) ) { + $doremote = 1; + } + } + else { + $opt{host} = '127.0.0.1'; + } + + if ( $opt{'ssl-ca'} ne 0 ) { + if ( -e -r -f $opt{'ssl-ca'} ) { + $remotestring .= " --ssl-ca=$opt{'ssl-ca'}"; + infoprint + "Will connect using ssl public key passed on the command line"; + return 1; + } + else { + badprint +"Attempted to use passed ssl public key, but it was not found or could not be read"; + exit 1; + } + } + + # Did we already get a username without password on the command line? + if ( $opt{user} ne 0 and $opt{pass} eq 0 ) { + $mysqllogin = "-u $opt{user} " . $remotestring; + my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint "Logged in using credentials passed on the command line"; + return 1; + } + else { + badprint + "Attempted to use login credentials, but they were invalid"; + exit 1; + } + } + + # Did we already get a username and password passed on the command line? + if ( $opt{user} ne 0 and $opt{pass} ne 0 ) { + $mysqllogin = "-u $opt{user} -p'$opt{pass}'" . $remotestring; + my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint "Logged in using credentials passed on the command line"; + return 1; + } + else { + badprint + "Attempted to use login credentials, but they were invalid"; + exit 1; + } + } + my $svcprop = which( "svcprop", $ENV{'PATH'} ); + if ( substr( $svcprop, 0, 1 ) =~ "/" ) { + + # We are on solaris + ( my $mysql_login = +`svcprop -p quickbackup/username svc:/network/mysql-quickbackup:default` + ) =~ s/\s+$//; + ( my $mysql_pass = +`svcprop -p quickbackup/password svc:/network/mysql-quickbackup:default` + ) =~ s/\s+$//; + if ( substr( $mysql_login, 0, 7 ) ne "svcprop" ) { + + # mysql-quickbackup is installed + $mysqllogin = "-u $mysql_login -p$mysql_pass"; + my $loginstatus = `mysqladmin $mysqllogin ping 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint "Logged in using credentials from mysql-quickbackup."; + return 1; + } + else { + badprint +"Attempted to use login credentials from mysql-quickbackup, but they failed."; + exit 1; + } + } + } + elsif ( -r "/etc/psa/.psa.shadow" and $doremote == 0 ) { + + # It's a Plesk box, use the available credentials + $mysqllogin = "-u admin -p`cat /etc/psa/.psa.shadow`"; + my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + unless ( $loginstatus =~ /mysqld is alive/ ) { + + # Plesk 10+ + $mysqllogin = + "-u admin -p`/usr/local/psa/bin/admin --show-password`"; + $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + unless ( $loginstatus =~ /mysqld is alive/ ) { + badprint +"Attempted to use login credentials from Plesk and Plesk 10+, but they failed."; + exit 1; + } + } + } + elsif ( -r "/usr/local/directadmin/conf/mysql.conf" and $doremote == 0 ) { + + # It's a DirectAdmin box, use the available credentials + my $mysqluser = + `cat /usr/local/directadmin/conf/mysql.conf | egrep '^user=.*'`; + my $mysqlpass = + `cat /usr/local/directadmin/conf/mysql.conf | egrep '^passwd=.*'`; + + $mysqluser =~ s/user=//; + $mysqluser =~ s/[\r\n]//; + $mysqlpass =~ s/passwd=//; + $mysqlpass =~ s/[\r\n]//; + + $mysqllogin = "-u $mysqluser -p$mysqlpass"; + + my $loginstatus = `mysqladmin ping $mysqllogin 2>&1`; + unless ( $loginstatus =~ /mysqld is alive/ ) { + badprint +"Attempted to use login credentials from DirectAdmin, but they failed."; + exit 1; + } + } + elsif ( -r "/etc/mysql/debian.cnf" + and $doremote == 0 + and $opt{'defaults-file'} eq '' ) + { + + # We have a Debian maintenance account, use it + $mysqllogin = "--defaults-file=/etc/mysql/debian.cnf"; + my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint + "Logged in using credentials from Debian maintenance account."; + return 1; + } + else { + badprint +"Attempted to use login credentials from Debian maintenance account, but they failed."; + exit 1; + } + } + elsif ( $opt{'defaults-file'} ne '' and -r "$opt{'defaults-file'}" ) { + + # defaults-file + debugprint "defaults file detected: $opt{'defaults-file'}"; + my $mysqlclidefaults = `$mysqlcmd --print-defaults`; + debugprint "MySQL Client Default File: $opt{'defaults-file'}"; + + $mysqllogin = "--defaults-file=" . $opt{'defaults-file'}; + my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint "Logged in using credentials from defaults file account."; + return 1; + } + } + else { + # It's not Plesk or Debian, we should try a login + debugprint "$mysqladmincmd $remotestring ping 2>&1"; + my $loginstatus = `$mysqladmincmd $remotestring ping 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + + # Login went just fine + $mysqllogin = " $remotestring "; + + # Did this go well because of a .my.cnf file or is there no password set? + my $userpath = `printenv HOME`; + if ( length($userpath) > 0 ) { + chomp($userpath); + } + unless ( -e "${userpath}/.my.cnf" or -e "${userpath}/.mylogin.cnf" ) + { + badprint +"Successfully authenticated with no password - SECURITY RISK!"; + } + return 1; + } + else { + if ( $opt{'noask'} == 1 ) { + badprint + "Attempted to use login credentials, but they were invalid"; + exit 1; + } + my ( $name, $password ); + + # If --user is defined no need to ask for username + if ( $opt{user} ne 0 ) { + $name = $opt{user}; + } + else { + print STDERR "Please enter your MySQL administrative login: "; + $name = ; + } + + # If --pass is defined no need to ask for password + if ( $opt{pass} ne 0 ) { + $password = $opt{pass}; + } + else { + print STDERR + "Please enter your MySQL administrative password: "; + system("stty -echo >$devnull 2>&1"); + $password = ; + system("stty echo >$devnull 2>&1"); + } + chomp($password); + chomp($name); + $mysqllogin = "-u $name"; + + if ( length($password) > 0 ) { + $mysqllogin .= " -p'$password'"; + } + $mysqllogin .= $remotestring; + my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + print STDERR ""; + if ( !length($password) ) { + + # Did this go well because of a .my.cnf file or is there no password set? + my $userpath = `printenv HOME`; + chomp($userpath); + unless ( -e "$userpath/.my.cnf" ) { + badprint +"Successfully authenticated with no password - SECURITY RISK!"; + } + } + return 1; + } + else { + badprint + "Attempted to use login credentials, but they were invalid."; + exit 1; + } + exit 1; + } + } +} + +# MySQL Request Array +sub select_array { + my $req = shift; + debugprint "PERFORM: $req "; + my @result = `$mysqlcmd $mysqllogin -Bse "\\w$req" 2>>/dev/null`; + if ( $? != 0 ) { + badprint "Failed to execute: $req"; + badprint "FAIL Execute SQL / return code: $?"; + debugprint "CMD : $mysqlcmd"; + debugprint "OPTIONS: $mysqllogin"; + debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; + + #exit $?; + } + debugprint "select_array: return code : $?"; + chomp(@result); + return @result; +} + +sub human_size { + my ( $size, $n ) = ( shift, 0 ); + ++$n and $size /= 1024 until $size < 1024; + return sprintf "%.2f %s", $size, (qw[ bytes KB MB GB ])[$n]; +} + +# MySQL Request one +sub select_one { + my $req = shift; + debugprint "PERFORM: $req "; + my $result = `$mysqlcmd $mysqllogin -Bse "\\w$req" 2>>/dev/null`; + if ( $? != 0 ) { + badprint "Failed to execute: $req"; + badprint "FAIL Execute SQL / return code: $?"; + debugprint "CMD : $mysqlcmd"; + debugprint "OPTIONS: $mysqllogin"; + debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; + + #exit $?; + } + debugprint "select_array: return code : $?"; + chomp($result); + return $result; +} + +# MySQL Request one +sub select_one_g { + my $pattern = shift; + + my $req = shift; + debugprint "PERFORM: $req "; + my @result = `$mysqlcmd $mysqllogin -re "\\w$req\\G" 2>>/dev/null`; + if ( $? != 0 ) { + badprint "Failed to execute: $req"; + badprint "FAIL Execute SQL / return code: $?"; + debugprint "CMD : $mysqlcmd"; + debugprint "OPTIONS: $mysqllogin"; + debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; + + #exit $?; + } + debugprint "select_array: return code : $?"; + chomp(@result); + return ( grep { /$pattern/ } @result )[0]; +} + +sub select_str_g { + my $pattern = shift; + + my $req = shift; + my $str = select_one_g $pattern, $req; + return () unless defined $str; + my @val = split /:/, $str; + shift @val; + return trim(@val); +} + +sub select_user_dbs { + return select_array( +"SELECT DISTINCT TABLE_SCHEMA FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'percona', 'sys')" + ); +} + +sub select_tables_db() { + my $schema = shift; + return select_array( +"SELECT DISTINCT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$schema'" + ); +} + +sub select_indexes_db() { + my $schema = shift; + return select_array( +"SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema'" + ); +} + +sub select_views_db { + my $schema = shift; + return select_array( +"SELECT DISTINCT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA='$schema'" + ); +} + +sub select_triggers_db { + my $schema = shift; + return select_array( +"SELECT DISTINCT TRIGGER_NAME FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='$schema'" + ); +} + +sub select_routines_db { + my $schema = shift; + return select_array( +"SELECT DISTINCT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='$schema'" + ); +} + +sub select_table_indexes_db { + my $schema = shift; + my $tbname = shift; + return select_array( +"SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$tbname'" + ); +} + +sub select_table_columns_db { + my $schema = shift; + my $table = shift; + return select_array( +"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$table'" + ); +} + +sub get_tuning_info { + my @infoconn = select_array "\\s"; + my ( $tkey, $tval ); + @infoconn = + grep { !/Threads:/ and !/Connection id:/ and !/pager:/ and !/Using/ } + @infoconn; + foreach my $line (@infoconn) { + if ( $line =~ /\s*(.*):\s*(.*)/ ) { + debugprint "$1 => $2"; + $tkey = $1; + $tval = $2; + chomp($tkey); + chomp($tval); + $result{'MySQL Client'}{$tkey} = $tval; + } + } + $result{'MySQL Client'}{'Client Path'} = $mysqlcmd; + $result{'MySQL Client'}{'Admin Path'} = $mysqladmincmd; + $result{'MySQL Client'}{'Authentication Info'} = $mysqllogin; + +} + +# Populates all of the variable and status hashes +my ( %mystat, %myvar, $dummyselect, %myrepl, %myslaves ); + +sub arr2hash { + my $href = shift; + my $harr = shift; + my $sep = shift; + $sep = '\s' unless defined($sep); + foreach my $line (@$harr) { + next if ( $line =~ m/^\*\*\*\*\*\*\*/ ); + $line =~ /([a-zA-Z_]*)\s*$sep\s*(.*)/; + $$href{$1} = $2; + debugprint "V: $1 = $2"; + } +} + +sub get_all_vars { + + # We need to initiate at least one query so that our data is useable + $dummyselect = select_one "SELECT VERSION()"; + if ( not defined($dummyselect) or $dummyselect eq "" ) { + badprint +"You probably did not get enough privileges for running MySQLTuner ..."; + exit(256); + } + $dummyselect =~ s/(.*?)\-.*/$1/; + debugprint "VERSION: " . $dummyselect . ""; + $result{'MySQL Client'}{'Version'} = $dummyselect; + + my @mysqlvarlist = select_array("SHOW VARIABLES"); + push( @mysqlvarlist, select_array("SHOW GLOBAL VARIABLES") ); + arr2hash( \%myvar, \@mysqlvarlist ); + $result{'Variables'} = \%myvar; + + my @mysqlstatlist = select_array("SHOW STATUS"); + push( @mysqlstatlist, select_array("SHOW GLOBAL STATUS") ); + arr2hash( \%mystat, \@mysqlstatlist ); + $result{'Status'} = \%mystat; + unless ( defined( $myvar{'innodb_support_xa'} ) ) { + $myvar{'innodb_support_xa'} = 'ON'; + } + $mystat{'Uptime'} = 1 + unless defined( $mystat{'Uptime'} ) + and $mystat{'Uptime'} > 0; + $myvar{'have_galera'} = "NO"; + if ( defined( $myvar{'wsrep_provider_options'} ) + && $myvar{'wsrep_provider_options'} ne "" + && $myvar{'wsrep_on'} ne "OFF" ) + { + $myvar{'have_galera'} = "YES"; + debugprint "Galera options: " . $myvar{'wsrep_provider_options'}; + } + + # Workaround for MySQL bug #59393 wrt. ignore-builtin-innodb + if ( ( $myvar{'ignore_builtin_innodb'} || "" ) eq "ON" ) { + $myvar{'have_innodb'} = "NO"; + } + + # Support GTID MODE FOR MARIADB + # Issue MariaDB GTID mode #513 + $myvar{'gtid_mode'} = 'ON' + if ( defined( $myvar{'gtid_current_pos'} ) + and $myvar{'gtid_current_pos'} ne '' ); + + # Whether the server uses a thread pool to handle client connections + # MariaDB: thread_handling = pool-of-threads + # MySQL: thread_handling = loaded-dynamically + $myvar{'have_threadpool'} = "NO"; + if ( + defined( $myvar{'thread_handling'} ) + and ( $myvar{'thread_handling'} eq 'pool-of-threads' + || $myvar{'thread_handling'} eq 'loaded-dynamically' ) + ) + { + $myvar{'have_threadpool'} = "YES"; + } + + # have_* for engines is deprecated and will be removed in MySQL 5.6; + # check SHOW ENGINES and set corresponding old style variables. + # Also works around MySQL bug #59393 wrt. skip-innodb + my @mysqlenginelist = select_array "SHOW ENGINES"; + foreach my $line (@mysqlenginelist) { + if ( $line =~ /^([a-zA-Z_]+)\s+(\S+)/ ) { + my $engine = lc($1); + + if ( $engine eq "federated" || $engine eq "blackhole" ) { + $engine .= "_engine"; + } + elsif ( $engine eq "berkeleydb" ) { + $engine = "bdb"; + } + my $val = ( $2 eq "DEFAULT" ) ? "YES" : $2; + $myvar{"have_$engine"} = $val; + $result{'Storage Engines'}{$engine} = $2; + } + } + + #debugprint Dumper(@mysqlenginelist); + + my @mysqlslave; + if ( mysql_version_eq(8) or mysql_version_ge( 10, 5 ) ) { + @mysqlslave = select_array("SHOW REPLICA STATUS\\G"); + } + else { + @mysqlslave = select_array("SHOW SLAVE STATUS\\G"); + } + arr2hash( \%myrepl, \@mysqlslave, ':' ); + $result{'Replication'}{'Status'} = \%myrepl; + + my @mysqlslaves; + if ( mysql_version_eq(8) or mysql_version_ge( 10, 5 ) ) { + @mysqlslaves = select_array "SHOW SLAVE STATUS"; + } + else { + @mysqlslaves = select_array("SHOW SLAVE HOSTS\\G"); + } + + my @lineitems = (); + foreach my $line (@mysqlslaves) { + debugprint "L: $line "; + @lineitems = split /\s+/, $line; + $myslaves{ $lineitems[0] } = $line; + $result{'Replication'}{'Slaves'}{ $lineitems[0] } = $lineitems[4]; + } +} + +sub remove_cr { + return map { + my $line = $_; + $line =~ s/\n$//g; + $line =~ s/^\s+$//g; + $line; + } @_; +} + +sub remove_empty { + grep { $_ ne '' } @_; +} + +sub grep_file_contents { + my $file = shift; + my $patt; +} + +sub get_file_contents { + my $file = shift; + open( my $fh, "<", $file ) or die "Can't open $file for read: $!"; + my @lines = <$fh>; + close $fh or die "Cannot close $file: $!"; + @lines = remove_cr @lines; + return @lines; +} + +sub get_basic_passwords { + return get_file_contents(shift); +} + +sub get_log_file_real_path { + my $file = shift; + my $hostname = shift; + my $datadir = shift; + if ( -f "$file" ) { + return $file; + } + elsif ( -f "$hostname.log" ) { + return "$hostname.log"; + } + elsif ( -f "$hostname.err" ) { + return "$hostname.err"; + } + elsif ( -f "$datadir$hostname.err" ) { + return "$datadir$hostname.err"; + } + elsif ( -f "$datadir$hostname.log" ) { + return "$datadir$hostname.log"; + } + elsif ( -f "$datadir" . "mysql_error.log" ) { + return "$datadir" . "mysql_error.log"; + } + elsif ( -f "/var/log/mysql.log" ) { + return "/var/log/mysql.log"; + } + elsif ( -f "/var/log/mysqld.log" ) { + return "/var/log/mysqld.log"; + } + elsif ( -f "/var/log/mysql/$hostname.err" ) { + return "/var/log/mysql/$hostname.err"; + } + elsif ( -f "/var/log/mysql/$hostname.log" ) { + return "/var/log/mysql/$hostname.log"; + } + elsif ( -f "/var/log/mysql/" . "mysql_error.log" ) { + return "/var/log/mysql/" . "mysql_error.log"; + } + else { + return $file; + } +} + +sub log_file_recommendations { + my $fh; + $myvar{'log_error'} = $opt{'server-log'} + || get_log_file_real_path( $myvar{'log_error'}, $myvar{'hostname'}, + $myvar{'datadir'} ); + + subheaderprint "Log file Recommendations"; + if ( "$myvar{'log_error'}" eq "stderr" ) { + badprint "log_error is set to $myvar{'log_error'} MT can't read stderr"; + return; + } + elsif ( $myvar{'log_error'} =~ /^(docker|podman|kubectl):(.*)/ ) { + open( $fh, '-|', "$1 logs --tail=$maxlines '$2'" ) + // die "Can't start $1 $!"; + goodprint "Log from cloud` $myvar{'log_error'} exists"; + } + elsif ( $myvar{'log_error'} =~ /^systemd:(.*)/ ) { + open( $fh, '-|', "journalctl -n $maxlines -b -u '$1'" ) + // die "Can't start journalctl $!"; + goodprint "Log journal` $myvar{'log_error'} exists"; + } + elsif ( -f "$myvar{'log_error'}" ) { + goodprint "Log file $myvar{'log_error'} exists"; + my $size = ( stat $myvar{'log_error'} )[7]; + infoprint "Log file: " + . $myvar{'log_error'} . " (" + . hr_bytes_rnd($size) . ")"; + + if ( $size > 0 ) { + goodprint "Log file $myvar{'log_error'} is not empty"; + if ( $size < 32 * 1024 * 1024 ) { + goodprint "Log file $myvar{'log_error'} is smaller than 32 Mb"; + } + else { + badprint "Log file $myvar{'log_error'} is bigger than 32 Mb"; + push @generalrec, + $myvar{'log_error'} + . " is > 32Mb, you should analyze why or implement a rotation log strategy such as logrotate!"; + } + } + else { + infoprint +"Log file $myvar{'log_error'} is empty. Assuming log-rotation. Use --server-log={file} for explicit file"; + return; + } + if ( !open( $fh, '<', $myvar{'log_error'} ) ) { + badprint "Log file $myvar{'log_error'} isn't readable."; + return; + } + goodprint "Log file $myvar{'log_error'} is readable."; + + if ( $maxlines * 80 < $size ) { + seek( $fh, -$maxlines * 80, 2 ); + <$fh>; # discard line fragment + } + } + else { + badprint "Log file $myvar{'log_error'} doesn't exist"; + return; + } + + my $numLi = 0; + my $nbWarnLog = 0; + my $nbErrLog = 0; + my @lastShutdowns; + my @lastStarts; + + while ( my $logLi = <$fh> ) { + chomp $logLi; + $numLi++; + debugprint "$numLi: $logLi" + if $logLi =~ /warning|error/i and $logLi !~ /Logging to/; + $nbErrLog++ if $logLi =~ /error/i and $logLi !~ /Logging to/; + $nbWarnLog++ if $logLi =~ /warning/i; + push @lastShutdowns, $logLi + if $logLi =~ /Shutdown complete/ and $logLi !~ /Innodb/i; + push @lastStarts, $logLi if $logLi =~ /ready for connections/; + } + close $fh; + + if ( $nbWarnLog > 0 ) { + badprint "$myvar{'log_error'} contains $nbWarnLog warning(s)."; + push @generalrec, "Check warning line(s) in $myvar{'log_error'} file"; + } + else { + goodprint "$myvar{'log_error'} doesn't contain any warning."; + } + if ( $nbErrLog > 0 ) { + badprint "$myvar{'log_error'} contains $nbErrLog error(s)."; + push @generalrec, "Check error line(s) in $myvar{'log_error'} file"; + } + else { + goodprint "$myvar{'log_error'} doesn't contain any error."; + } + + infoprint scalar @lastStarts . " start(s) detected in $myvar{'log_error'}"; + my $nStart = 0; + my $nEnd = 10; + if ( scalar @lastStarts < $nEnd ) { + $nEnd = scalar @lastStarts; + } + for my $startd ( reverse @lastStarts[ -$nEnd .. -1 ] ) { + $nStart++; + infoprint "$nStart) $startd"; + } + infoprint scalar @lastShutdowns + . " shutdown(s) detected in $myvar{'log_error'}"; + $nStart = 0; + $nEnd = 10; + if ( scalar @lastShutdowns < $nEnd ) { + $nEnd = scalar @lastShutdowns; + } + for my $shutd ( reverse @lastShutdowns[ -$nEnd .. -1 ] ) { + $nStart++; + infoprint "$nStart) $shutd"; + } + + #exit 0; +} + +sub cve_recommendations { + subheaderprint "CVE Security Recommendations"; + unless ( defined( $opt{cvefile} ) && -f "$opt{cvefile}" ) { + infoprint "Skipped due to --cvefile option undefined"; + return; + } + +#$mysqlvermajor=10; +#$mysqlverminor=1; +#$mysqlvermicro=17; +#prettyprint "Look for related CVE for $myvar{'version'} or lower in $opt{cvefile}"; + my $cvefound = 0; + open( my $fh, "<", $opt{cvefile} ) + or die "Can't open $opt{cvefile} for read: $!"; + while ( my $cveline = <$fh> ) { + my @cve = split( ';', $cveline ); + debugprint +"Comparing $mysqlvermajor\.$mysqlverminor\.$mysqlvermicro with $cve[1]\.$cve[2]\.$cve[3] : " + . ( mysql_version_le( $cve[1], $cve[2], $cve[3] ) ? '<=' : '>' ); + + # Avoid not major/minor version corresponding CVEs + next + unless ( int( $cve[1] ) == $mysqlvermajor + && int( $cve[2] ) == $mysqlverminor ); + if ( int( $cve[3] ) >= $mysqlvermicro ) { + badprint "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]"; + $result{'CVE'}{'List'}{$cvefound} = + "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]"; + $cvefound++; + } + } + close $fh or die "Cannot close $opt{cvefile}: $!"; + $result{'CVE'}{'nb'} = $cvefound; + + my $cve_warning_notes = ""; + if ( $cvefound == 0 ) { + goodprint "NO SECURITY CVE FOUND FOR YOUR VERSION"; + return; + } + if ( $mysqlvermajor eq 5 and $mysqlverminor eq 5 ) { + infoprint + "False positive CVE(s) for MySQL and MariaDB 5.5.x can be found."; + infoprint "Check careful each CVE for those particular versions"; + } + badprint $cvefound . " CVE(s) found for your MySQL release."; + push( @generalrec, + $cvefound + . " CVE(s) found for your MySQL release. Consider upgrading your version !" + ); +} + +sub get_opened_ports { + my @opened_ports = `netstat -ltn`; + @opened_ports = map { + my $v = $_; + $v =~ s/.*:(\d+)\s.*$/$1/; + $v =~ s/\D//g; + $v; + } @opened_ports; + @opened_ports = sort { $a <=> $b } grep { !/^$/ } @opened_ports; + + #debugprint Dumper \@opened_ports; + $result{'Network'}{'TCP Opened'} = \@opened_ports; + return @opened_ports; +} + +sub is_open_port { + my $port = shift; + if ( grep { /^$port$/ } get_opened_ports ) { + return 1; + } + return 0; +} + +sub get_process_memory { + my $pid = shift; + my @mem = `ps -p $pid -o rss`; + return 0 if scalar @mem != 2; + return $mem[1] * 1024; +} + +sub get_other_process_memory { + return 0 if ( $opt{tbstat} == 0 ); + my @procs = `ps eaxo pid,command`; + @procs = map { + my $v = $_; + $v =~ s/.*PID.*//; + $v =~ s/.*mysqld.*//; + $v =~ s/.*\[.*\].*//; + $v =~ s/^\s+$//g; + $v =~ s/.*PID.*CMD.*//; + $v =~ s/.*systemd.*//; + $v =~ s/\s*?(\d+)\s*.*/$1/g; + $v; + } @procs; + @procs = remove_cr @procs; + @procs = remove_empty @procs; + my $totalMemOther = 0; + map { $totalMemOther += get_process_memory($_); } @procs; + return $totalMemOther; +} + +sub get_os_release { + if ( -f "/etc/lsb-release" ) { + my @info_release = get_file_contents "/etc/lsb-release"; + my $os_release = $info_release[3]; + $os_release =~ s/.*="//; + $os_release =~ s/"$//; + return $os_release; + } + + if ( -f "/etc/system-release" ) { + my @info_release = get_file_contents "/etc/system-release"; + return $info_release[0]; + } + + if ( -f "/etc/os-release" ) { + my @info_release = get_file_contents "/etc/os-release"; + my $os_release = $info_release[0]; + $os_release =~ s/.*="//; + $os_release =~ s/"$//; + return $os_release; + } + + if ( -f "/etc/issue" ) { + my @info_release = get_file_contents "/etc/issue"; + my $os_release = $info_release[0]; + $os_release =~ s/\s+\\n.*//; + return $os_release; + } + return "Unknown OS release"; +} + +sub get_fs_info { + my @sinfo = `df -P | grep '%'`; + my @iinfo = `df -Pi| grep '%'`; + shift @sinfo; + shift @iinfo; + + foreach my $info (@sinfo) { + + #exit(0); + if ( $info =~ /.*?(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.*)$/ ) { + next if $5 =~ m{(run|dev|sys|proc|snap|init)}; + if ( $4 > 85 ) { + badprint "mount point $5 is using $4 % total space (" + . human_size( $2 * 1024 ) . " / " + . human_size( $1 * 1024 ) . ")"; + push( @generalrec, "Add some space to $4 mountpoint." ); + } + else { + infoprint "mount point $5 is using $4 % total space (" + . human_size( $2 * 1024 ) . " / " + . human_size( $1 * 1024 ) . ")"; + } + $result{'Filesystem'}{'Space Pct'}{$5} = $4; + $result{'Filesystem'}{'Used Space'}{$5} = $2; + $result{'Filesystem'}{'Free Space'}{$5} = $3; + $result{'Filesystem'}{'Total Space'}{$5} = $1; + } + } + + @iinfo = map { + my $v = $_; + $v =~ s/.*\s(\d+)%\s+(.*)/$1\t$2/g; + $v; + } @iinfo; + foreach my $info (@iinfo) { + next if $info =~ m{(\d+)\t/(run|dev|sys|proc)($|/)}; + if ( $info =~ /(\d+)\t(.*)/ ) { + if ( $1 > 85 ) { + badprint "mount point $2 is using $1 % of max allowed inodes"; + push( @generalrec, +"Cleanup files from $2 mountpoint or reformat you filesystem." + ); + } + else { + infoprint "mount point $2 is using $1 % of max allowed inodes"; + } + $result{'Filesystem'}{'Inode Pct'}{$2} = $1; + } + } +} + +sub merge_hash { + my $h1 = shift; + my $h2 = shift; + my %result = {}; + foreach my $substanceref ( $h1, $h2 ) { + while ( my ( $k, $v ) = each %$substanceref ) { + next if ( exists $result{$k} ); + $result{$k} = $v; + } + } + return \%result; +} + +sub is_virtual_machine { + if ( $^O eq 'linux' ) { + my $isVm = `grep -Ec '^flags.*\ hypervisor\ ' /proc/cpuinfo`; + return ( $isVm == 0 ? 0 : 1 ); + } + + if ( $^O eq 'freebsd' ) { + my $isVm = `sysctl -n kern.vm_guest`; + chomp $isVm; + print "FARK DEBUG isVm=[$isVm]"; + return ( $isVm eq 'none' ? 0 : 1 ); + } + return 0; +} + +sub infocmd { + my $cmd = "@_"; + debugprint "CMD: $cmd"; + my @result = `$cmd`; + @result = remove_cr @result; + for my $l (@result) { + infoprint "$l"; + } +} + +sub infocmd_tab { + my $cmd = "@_"; + debugprint "CMD: $cmd"; + my @result = `$cmd`; + @result = remove_cr @result; + for my $l (@result) { + infoprint "\t$l"; + } +} + +sub infocmd_one { + my $cmd = "@_"; + my @result = `$cmd 2>&1`; + @result = remove_cr @result; + return join ', ', @result; +} + +sub get_kernel_info { + my @params = ( + 'fs.aio-max-nr', 'fs.aio-nr', + 'fs.file-max', 'sunrpc.tcp_fin_timeout', + 'sunrpc.tcp_max_slot_table_entries', 'sunrpc.tcp_slot_table_entries', + 'vm.swappiness' + ); + infoprint "Information about kernel tuning:"; + foreach my $param (@params) { + infocmd_tab("sysctl $param 2>/dev/null"); + $result{'OS'}{'Config'}{$param} = `sysctl -n $param 2>/dev/null`; + } + if ( `sysctl -n vm.swappiness` > 10 ) { + badprint + "Swappiness is > 10, please consider having a value lower than 10"; + push @generalrec, "setup swappiness lower or equals to 10"; + push @adjvars, + 'vm.swappiness <= 10 (echo 10 > /proc/sys/vm/swappiness)'; + } + else { + infoprint "Swappiness is < 10."; + } + + # only if /proc/sys/sunrpc exists + my $tcp_slot_entries = + `sysctl -n sunrpc.tcp_slot_table_entries 2>/dev/null`; + if ( -f "/proc/sys/sunrpc" + and ( $tcp_slot_entries eq '' or $tcp_slot_entries < 100 ) ) + { + badprint +"Initial TCP slot entries is < 1M, please consider having a value greater than 100"; + push @generalrec, "setup Initial TCP slot entries greater than 100"; + push @adjvars, +'sunrpc.tcp_slot_table_entries > 100 (echo 128 > /proc/sys/sunrpc/tcp_slot_table_entries)'; + } + else { + infoprint "TCP slot entries is > 100."; + } + + if ( -f "/proc/sys/fs/aio-max-nr" ) { + if ( `sysctl -n fs.aio-max-nr` < 1000000 ) { + badprint +"Max running total of the number of events is < 1M, please consider having a value greater than 1M"; + push @generalrec, "setup Max running number events greater than 1M"; + push @adjvars, + 'fs.aio-max-nr > 1M (echo 1048576 > /proc/sys/fs/aio-max-nr)'; + } + else { + infoprint "Max Number of AIO events is > 1M."; + } + } +} + +sub get_system_info { + $result{'OS'}{'Release'} = get_os_release(); + infoprint get_os_release; + if (is_virtual_machine) { + infoprint "Machine type : Virtual machine"; + $result{'OS'}{'Virtual Machine'} = 'YES'; + } + else { + infoprint "Machine type : Physical machine"; + $result{'OS'}{'Virtual Machine'} = 'NO'; + } + + $result{'Network'}{'Connected'} = 'NO'; + `ping -c 1 ipecho.net &>/dev/null`; + my $isConnected = $?; + if ( $? == 0 ) { + infoprint "Internet : Connected"; + $result{'Network'}{'Connected'} = 'YES'; + } + else { + badprint "Internet : Disconnected"; + } + $result{'OS'}{'NbCore'} = cpu_cores; + infoprint "Number of Core CPU : " . cpu_cores; + $result{'OS'}{'Type'} = `uname -o`; + infoprint "Operating System Type : " . infocmd_one "uname -o"; + $result{'OS'}{'Kernel'} = `uname -r`; + infoprint "Kernel Release : " . infocmd_one "uname -r"; + $result{'OS'}{'Hostname'} = `hostname`; + $result{'Network'}{'Internal Ip'} = `hostname -I`; + infoprint "Hostname : " . infocmd_one "hostname"; + infoprint "Network Cards : "; + infocmd_tab "ifconfig| grep -A1 mtu"; + infoprint "Internal IP : " . infocmd_one "hostname -I"; + $result{'Network'}{'Internal Ip'} = `ifconfig| grep -A1 mtu`; + my $httpcli = get_http_cli(); + infoprint "HTTP client found: $httpcli" if defined $httpcli; + + my $ext_ip = ""; + if ( $httpcli =~ /curl$/ ) { + $ext_ip = infocmd_one "$httpcli -m 3 ipecho.net/plain"; + } + elsif ( $httpcli =~ /wget$/ ) { + + $ext_ip = infocmd_one "$httpcli -t 1 -T 3 -q -O - ipecho.net/plain"; + } + infoprint "External IP : " . $ext_ip; + $result{'Network'}{'External Ip'} = $ext_ip; + badprint + "External IP : Can't check because of Internet connectivity" + unless defined($httpcli); + infoprint "Name Servers : " + . infocmd_one "grep 'nameserver' /etc/resolv.conf \| awk '{print \$2}'"; + infoprint "Logged In users : "; + infocmd_tab "who"; + $result{'OS'}{'Logged users'} = `who`; + infoprint "Ram Usages in Mb : "; + infocmd_tab "free -m | grep -v +"; + $result{'OS'}{'Free Memory RAM'} = `free -m | grep -v +`; + infoprint "Load Average : "; + infocmd_tab "top -n 1 -b | grep 'load average:'"; + $result{'OS'}{'Load Average'} = `top -n 1 -b | grep 'load average:'`; + + infoprint "System Uptime : "; + infocmd_tab "uptime"; + $result{'OS'}{'Uptime'} = `uptime`; +} + +sub system_recommendations { + return if ( $opt{sysstat} == 0 ); + subheaderprint "System Linux Recommendations"; + my $os = `uname`; + unless ( $os =~ /Linux/i ) { + infoprint "Skipped due to non Linux server"; + return; + } + prettyprint "Look for related Linux system recommendations"; + + #prettyprint '-'x78; + get_system_info(); + my $omem = get_other_process_memory; + infoprint "User process except mysqld used " + . hr_bytes_rnd($omem) . " RAM."; + if ( ( 0.15 * $physical_memory ) < $omem ) { + badprint +"Other user process except mysqld used more than 15% of total physical memory " + . percentage( $omem, $physical_memory ) . "% (" + . hr_bytes_rnd($omem) . " / " + . hr_bytes_rnd($physical_memory) . ")"; + push( @generalrec, +"Consider stopping or dedicate server for additional process other than mysqld." + ); + push( @adjvars, +"DON'T APPLY SETTINGS BECAUSE THERE ARE TOO MANY PROCESSES RUNNING ON THIS SERVER. OOM KILL CAN OCCUR!" + ); + } + else { + infoprint +"Other user process except mysqld used less than 15% of total physical memory " + . percentage( $omem, $physical_memory ) . "% (" + . hr_bytes_rnd($omem) . " / " + . hr_bytes_rnd($physical_memory) . ")"; + } + + if ( $opt{'maxportallowed'} > 0 ) { + my @opened_ports = get_opened_ports; + infoprint "There is " + . scalar @opened_ports + . " listening port(s) on this server."; + if ( scalar(@opened_ports) > $opt{'maxportallowed'} ) { + badprint "There is too many listening ports: " + . scalar(@opened_ports) + . " opened > " + . $opt{'maxportallowed'} + . "allowed."; + push( @generalrec, +"Consider dedicating a server for your database installation with less services running on !" + ); + } + else { + goodprint "There is less than " + . $opt{'maxportallowed'} + . " opened ports on this server."; + } + } + + foreach my $banport (@banned_ports) { + if ( is_open_port($banport) ) { + badprint "Banned port: $banport is opened.."; + push( @generalrec, +"Port $banport is opened. Consider stopping program handling this port." + ); + } + else { + goodprint "$banport is not opened."; + } + } + + subheaderprint "Filesystem Linux Recommendations"; + get_fs_info; + subheaderprint "Kernel Information Recommendations"; + get_kernel_info; +} + +sub security_recommendations { + subheaderprint "Security Recommendations"; + + if ( mysql_version_eq(8) ) { + infoprint "Skipped due to unsupported feature for MySQL 8"; + return; + } + + #exit 0; + if ( $opt{skippassword} eq 1 ) { + infoprint "Skipped due to --skippassword option"; + return; + } + + my $PASS_COLUMN_NAME = 'password'; + + # New table schema available since mysql-5.7 and mariadb-10.2 + # But need to be checked + if ( $myvar{'version'} =~ /5\.7|10\.[2-5]\..*MariaDB*/ ) { + my $password_column_exists = +`$mysqlcmd $mysqllogin -Bse "SELECT 1 FROM information_schema.columns WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME = 'password'" 2>>/dev/null`; + my $authstring_column_exists = +`$mysqlcmd $mysqllogin -Bse "SELECT 1 FROM information_schema.columns WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME = 'authentication_string'" 2>>/dev/null`; + if ( $password_column_exists && $authstring_column_exists ) { + $PASS_COLUMN_NAME = +"IF(plugin='mysql_native_password', authentication_string, password)"; + } + elsif ($authstring_column_exists) { + $PASS_COLUMN_NAME = 'authentication_string'; + } + elsif ( !$password_column_exists ) { + infoprint "Skipped due to none of known auth columns exists"; + return; + } + } + debugprint "Password column = $PASS_COLUMN_NAME"; + + # Looking for Anonymous users + my @mysqlstatlist = select_array +"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL"; + + #debugprint Dumper \@mysqlstatlist; + + #exit 0; + if (@mysqlstatlist) { + push( @generalrec, + "Remove Anonymous User accounts - there are " + . scalar(@mysqlstatlist) + . " anonymous accounts." ); + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + badprint "User " + . $line + . " is an anonymous account. Remove with DROP USER " + . $line . ";"; + } + } + else { + goodprint "There are no anonymous accounts for any database users"; + } + if ( mysql_version_le( 5, 1 ) ) { + badprint "No more password checks for MySQL version <=5.1"; + badprint "MySQL version <=5.1 are deprecated and end of support."; + return; + } + + # Looking for Empty Password + if ( mysql_version_ge( 10, 4 ) ) { + @mysqlstatlist = select_array +q{SELECT CONCAT(QUOTE(user), '@', QUOTE(host)) FROM mysql.global_priv WHERE + user != '' + AND JSON_CONTAINS(Priv, '"mysql_native_password"', '$.plugin') AND JSON_CONTAINS(Priv, '""', '$.authentication_string') + AND NOT JSON_CONTAINS(Priv, 'true', '$.account_locked')}; + } + else { + @mysqlstatlist = select_array +"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE ($PASS_COLUMN_NAME = '' OR $PASS_COLUMN_NAME IS NULL) + AND user != '' + /*!50501 AND plugin NOT IN ('auth_socket', 'unix_socket', 'win_socket', 'auth_pam_compat') */ + /*!80000 AND account_locked = 'N' AND password_expired = 'N' */"; + } + if (@mysqlstatlist) { + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + badprint "User '" . $line . "' has no password set."; + push( @generalrec, +"Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');" + ); + } + } + else { + goodprint "All database users have passwords assigned"; + } + + if ( mysql_version_ge( 5, 7 ) ) { + my $valPlugin = select_one( +"select count(*) from information_schema.plugins where PLUGIN_NAME='validate_password' AND PLUGIN_STATUS='ACTIVE'" + ); + if ( $valPlugin >= 1 ) { + infoprint +"Bug #80860 MySQL 5.7: Avoid testing password when validate_password is activated"; + return; + } + } + + # Looking for User with user/ uppercase /capitalise user as password + @mysqlstatlist = select_array +"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE user != '' AND (CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(user) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(UPPER(user)) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(CONCAT(UPPER(LEFT(User, 1)), SUBSTRING(User, 2, LENGTH(User)))))"; + if (@mysqlstatlist) { + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + badprint "User " . $line . " has user name as password."; + push( @generalrec, +"Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');" + ); + } + } + + @mysqlstatlist = select_array + "SELECT CONCAT(QUOTE(user), '\@', host) FROM mysql.user WHERE HOST='%'"; + if (@mysqlstatlist) { + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + my $luser = ( split /@/, $line )[0]; + badprint "User " . $line + . " does not specify hostname restrictions."; + push( @generalrec, +"Restrict Host for $luser\@'%' to $luser\@LimitedIPRangeOrLocalhost" + ); + push( @generalrec, + "RENAME USER $luser\@'%' TO " + . $luser + . "\@LimitedIPRangeOrLocalhost;" ); + } + } + + unless ( -f $basic_password_files ) { + badprint "There is no basic password file list!"; + return; + } + + my @passwords = get_basic_passwords $basic_password_files; + infoprint "There are " + . scalar(@passwords) + . " basic passwords in the list."; + my $nbins = 0; + my $passreq; + if (@passwords) { + my $nbInterPass = 0; + foreach my $pass (@passwords) { + $nbInterPass++; + + $pass =~ s/\s//g; + $pass =~ s/\'/\\\'/g; + chomp($pass); + + # Looking for User with user/ uppercase /capitalise weak password + @mysqlstatlist = + select_array +"SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE $PASS_COLUMN_NAME = PASSWORD('" + . $pass + . "') OR $PASS_COLUMN_NAME = PASSWORD(UPPER('" + . $pass + . "')) OR $PASS_COLUMN_NAME = PASSWORD(CONCAT(UPPER(LEFT('" + . $pass + . "', 1)), SUBSTRING('" + . $pass + . "', 2, LENGTH('" + . $pass . "'))))"; + debugprint "There is " . scalar(@mysqlstatlist) . " items."; + if (@mysqlstatlist) { + foreach my $line (@mysqlstatlist) { + chomp($line); + badprint "User '" . $line + . "' is using weak password: $pass in a lower, upper or capitalize derivative version."; + + push( @generalrec, +"Set up a Secure Password for $line user: SET PASSWORD FOR '" + . ( split /@/, $line )[0] . "'\@'" + . ( split /@/, $line )[1] + . "' = PASSWORD('secure_password');" ); + $nbins++; + } + } + debugprint "$nbInterPass / " . scalar(@passwords) + if ( $nbInterPass % 1000 == 0 ); + } + } + if ( $nbins > 0 ) { + push( @generalrec, + $nbins + . " user(s) used basic or weak password from basic dictionary." ); + } +} + +sub get_replication_status { + subheaderprint "Replication Metrics"; + infoprint "Galera Synchronous replication: " . $myvar{'have_galera'}; + if ( scalar( keys %myslaves ) == 0 ) { + infoprint "No replication slave(s) for this server."; + } + else { + infoprint "This server is acting as master for " + . scalar( keys %myslaves ) + . " server(s)."; + } + infoprint "Binlog format: " . $myvar{'binlog_format'}; + infoprint "XA support enabled: " . $myvar{'innodb_support_xa'}; + + infoprint "Semi synchronous replication Master: " + . ( + ( defined( $myvar{'rpl_semi_sync_master_enabled'} ) or defined( $myvar{'rpl_semi_sync_source_enabled'} ) ) + ? ( $myvar{'rpl_semi_sync_master_enabled'} // $myvar{'rpl_semi_sync_source_enabled'} ) + : 'Not Activated' + ); + infoprint "Semi synchronous replication Slave: " + . ( + ( defined( $myvar{'rpl_semi_sync_slave_enabled'} ) or defined( $myvar{'rpl_semi_sync_replica_enabled'} ) ) + ? ( $myvar{'rpl_semi_sync_slave_enabled'} // $myvar{'rpl_semi_sync_replica_enabled'} ) + : 'Not Activated' + ); + if ( scalar( keys %myrepl ) == 0 and scalar( keys %myslaves ) == 0 ) { + infoprint "This is a standalone server"; + return; + } + if ( scalar( keys %myrepl ) == 0 ) { + infoprint + "No replication setup for this server or replication not started."; + return; + } + + $result{'Replication'}{'status'} = \%myrepl; + my ($io_running) = $myrepl{'Slave_IO_Running'} // $myrepl{'Replica_IO_Running'}; + debugprint "IO RUNNING: $io_running "; + my ($sql_running) = $myrepl{'Slave_SQL_Running'} // $myrepl{'Replica_SQL_Running'}; + debugprint "SQL RUNNING: $sql_running "; + my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'} // $myrepl{'Seconds_Behind_Source'} ; + debugprint "SECONDS : $seconds_behind_master "; + + if ( defined($io_running) + and ( $io_running !~ /yes/i or $sql_running !~ /yes/i ) ) + { + badprint + "This replication slave is not running but seems to be configured."; + } + if ( defined($io_running) + && $io_running =~ /yes/i + && $sql_running =~ /yes/i ) + { + if ( $myvar{'read_only'} eq 'OFF' ) { + badprint +"This replication slave is running with the read_only option disabled."; + } + else { + goodprint +"This replication slave is running with the read_only option enabled."; + } + if ( $seconds_behind_master > 0 ) { + badprint +"This replication slave is lagging and slave has $seconds_behind_master second(s) behind master host."; + } + else { + goodprint "This replication slave is up to date with master."; + } + } +} + +# https://endoflife.software/applications/databases/mysql +# https://endoflife.date/mariadb +sub validate_mysql_version { + ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = + $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; + $mysqlverminor ||= 0; + $mysqlvermicro ||= 0; + + if ( mysql_version_eq(8) + or mysql_version_eq( 5, 7 ) + or mysql_version_eq( 10, 3 ) + or mysql_version_eq( 10, 4 ) + or mysql_version_eq( 10, 5 ) + or mysql_version_eq( 10, 6 ) + ) + { + goodprint "Currently running supported MySQL version " + . $myvar{'version'} . ""; + return; + } else { + badprint "Your MySQL version " + . $myvar{'version'} + . " is EOL software! Upgrade soon!"; + push ( @generalrec, "You are using n unsupported version for production environments"); + push ( @generalrec, "Upgrade as soon as possible to a supported version !"); + + } +} + +# Checks if MySQL version is equal to (major, minor, micro) +sub mysql_version_eq { + my ( $maj, $min, $mic ) = @_; + my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = + $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; + + return int($mysqlvermajor) == int($maj) + if ( !defined($min) && !defined($mic) ); + return int($mysqlvermajor) == int($maj) && int($mysqlverminor) == int($min) + if ( !defined($mic) ); + return ( int($mysqlvermajor) == int($maj) + && int($mysqlverminor) == int($min) + && int($mysqlvermicro) == int($mic) ); +} + +# Checks if MySQL version is greater than equal to (major, minor, micro) +sub mysql_version_ge { + my ( $maj, $min, $mic ) = @_; + $min ||= 0; + $mic ||= 0; + my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = + $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; + + return + int($mysqlvermajor) > int($maj) + || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) > int($min) ) + || ( int($mysqlvermajor) == int($maj) + && int($mysqlverminor) == int($min) + && int($mysqlvermicro) >= int($mic) ); +} + +# Checks if MySQL version is lower than equal to (major, minor, micro) +sub mysql_version_le { + my ( $maj, $min, $mic ) = @_; + $min ||= 0; + $mic ||= 0; + my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = + $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; + return + int($mysqlvermajor) < int($maj) + || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) < int($min) ) + || ( int($mysqlvermajor) == int($maj) + && int($mysqlverminor) == int($min) + && int($mysqlvermicro) <= int($mic) ); +} + +# Checks if MySQL micro version is lower than equal to (major, minor, micro) +sub mysql_micro_version_le { + my ( $maj, $min, $mic ) = @_; + my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = + $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; + + return $mysqlvermajor == $maj + && ( $mysqlverminor == $min + && $mysqlvermicro <= $mic ); +} + +# Checks for 32-bit boxes with more than 2GB of RAM +my ($arch); + +sub check_architecture { + if ( $doremote eq 1 ) { return; } + if ( `uname` =~ /SunOS/ && `isainfo -b` =~ /64/ ) { + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` !~ /SunOS/ && `uname -m` =~ /(64|s390x)/ ) { + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` =~ /AIX/ && `bootinfo -K` =~ /64/ ) { + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` =~ /NetBSD|OpenBSD/ && `sysctl -b hw.machine` =~ /64/ ) { + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` =~ /FreeBSD/ && `sysctl -b hw.machine_arch` =~ /64/ ) { + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /Power Macintosh/ ) { + +# Darwin box.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:57:01 PDT 2009; root:xnu1228.15.4~1/RELEASE_PPC Power Macintosh + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /x86_64/ ) { + +# Darwin gibas.local 12.3.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64 + $arch = 64; + goodprint "Operating on 64-bit architecture"; + } + else { + $arch = 32; + if ( $physical_memory > 2147483648 ) { + badprint +"Switch to 64-bit OS - MySQL cannot currently use all of your RAM"; + } + else { + goodprint "Operating on 32-bit architecture with less than 2GB RAM"; + } + } + $result{'OS'}{'Architecture'} = "$arch bits"; + +} + +# Start up a ton of storage engine counts/statistics +my ( %enginestats, %enginecount, $fragtables ); + +sub check_storage_engines { + subheaderprint "Storage Engine Statistics"; + if ( $opt{skipsize} eq 1 ) { + infoprint "Skipped due to --skipsize option"; + return; + } + + my $engines; + if ( mysql_version_ge( 5, 5 ) ) { + my @engineresults = select_array +"SELECT ENGINE,SUPPORT FROM information_schema.ENGINES ORDER BY ENGINE ASC"; + foreach my $line (@engineresults) { + my ( $engine, $engineenabled ); + ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/; + $result{'Engine'}{$engine}{'Enabled'} = $engineenabled; + $engines .= + ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" ) + ? greenwrap "+" . $engine . " " + : redwrap "-" . $engine . " "; + } + } + elsif ( mysql_version_ge( 5, 1, 5 ) ) { + my @engineresults = select_array +"SELECT ENGINE,SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('performance_schema','MyISAM','MERGE','MEMORY') ORDER BY ENGINE ASC"; + foreach my $line (@engineresults) { + my ( $engine, $engineenabled ); + ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/; + $result{'Engine'}{$engine}{'Enabled'} = $engineenabled; + $engines .= + ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" ) + ? greenwrap "+" . $engine . " " + : redwrap "-" . $engine . " "; + } + } + else { + $engines .= + ( defined $myvar{'have_archive'} && $myvar{'have_archive'} eq "YES" ) + ? greenwrap "+Archive " + : redwrap "-Archive "; + $engines .= + ( defined $myvar{'have_bdb'} && $myvar{'have_bdb'} eq "YES" ) + ? greenwrap "+BDB " + : redwrap "-BDB "; + $engines .= + ( defined $myvar{'have_federated_engine'} + && $myvar{'have_federated_engine'} eq "YES" ) + ? greenwrap "+Federated " + : redwrap "-Federated "; + $engines .= + ( defined $myvar{'have_innodb'} && $myvar{'have_innodb'} eq "YES" ) + ? greenwrap "+InnoDB " + : redwrap "-InnoDB "; + $engines .= + ( defined $myvar{'have_isam'} && $myvar{'have_isam'} eq "YES" ) + ? greenwrap "+ISAM " + : redwrap "-ISAM "; + $engines .= + ( defined $myvar{'have_ndbcluster'} + && $myvar{'have_ndbcluster'} eq "YES" ) + ? greenwrap "+NDBCluster " + : redwrap "-NDBCluster "; + } + + my @dblist = grep { $_ ne 'lost+found' } select_array "SHOW DATABASES"; + + $result{'Databases'}{'List'} = [@dblist]; + infoprint "Status: $engines"; + if ( mysql_version_ge( 5, 1, 5 ) ) { + +# MySQL 5 servers can have table sizes calculated quickly from information schema + my @templist = select_array +"SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE),SUM(DATA_LENGTH),SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"; + + my ( $engine, $size, $count, $dsize, $isize ); + foreach my $line (@templist) { + ( $engine, $size, $count, $dsize, $isize ) = + $line =~ /([a-zA-Z_]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/; + debugprint "Engine Found: $engine"; + next unless ( defined($engine) or trim($engine) eq '' ); + $size = 0 unless ( defined($size) or trim($engine) eq '' ); + $isize = 0 unless ( defined($isize) or trim($engine) eq '' ); + $dsize = 0 unless ( defined($dsize) or trim($engine) eq '' ); + $count = 0 unless ( defined($count) or trim($engine) eq '' ); + $enginestats{$engine} = $size; + $enginecount{$engine} = $count; + $result{'Engine'}{$engine}{'Table Number'} = $count; + $result{'Engine'}{$engine}{'Total Size'} = $size; + $result{'Engine'}{$engine}{'Data Size'} = $dsize; + $result{'Engine'}{$engine}{'Index Size'} = $isize; + } + my $not_innodb = ''; + if ( not defined $result{'Variables'}{'innodb_file_per_table'} ) { + $not_innodb = "AND NOT ENGINE='InnoDB'"; + } + elsif ( $result{'Variables'}{'innodb_file_per_table'} eq 'OFF' ) { + $not_innodb = "AND NOT ENGINE='InnoDB'"; + } + $result{'Tables'}{'Fragmented tables'} = + [ select_array +"SELECT CONCAT(CONCAT(TABLE_SCHEMA, '.'), TABLE_NAME),cast(DATA_FREE as signed) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb" + ]; + $fragtables = scalar @{ $result{'Tables'}{'Fragmented tables'} }; + + } + else { + + # MySQL < 5 servers take a lot of work to get table sizes + my @tblist; + +# Now we build a database list, and loop through it to get storage engine stats for tables + foreach my $db (@dblist) { + chomp($db); + if ( $db eq "information_schema" + or $db eq "performance_schema" + or $db eq "mysql" + or $db eq "lost+found" ) + { + next; + } + my @ixs = ( 1, 6, 9 ); + if ( !mysql_version_ge( 4, 1 ) ) { + + # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column + @ixs = ( 1, 5, 8 ); + } + push( @tblist, + map { [ (split)[@ixs] ] } + select_array "SHOW TABLE STATUS FROM \\\`$db\\\`" ); + } + + # Parse through the table list to generate storage engine counts/statistics + $fragtables = 0; + foreach my $tbl (@tblist) { + + #debugprint "Data dump " . Dumper(@$tbl); + my ( $engine, $size, $datafree ) = @$tbl; + next if $engine eq 'NULL' or not defined($engine); + $size = 0 if $size eq 'NULL' or not defined($size); + $datafree = 0 if $datafree eq 'NULL' or not defined($datafree); + if ( defined $enginestats{$engine} ) { + $enginestats{$engine} += $size; + $enginecount{$engine} += 1; + } + else { + $enginestats{$engine} = $size; + $enginecount{$engine} = 1; + } + if ( $datafree > 0 ) { + $fragtables++; + } + } + } + while ( my ( $engine, $size ) = each(%enginestats) ) { + infoprint "Data in $engine tables: " + . hr_bytes($size) + . " (Tables: " + . $enginecount{$engine} . ")" . ""; + } + + # If the storage engine isn't being used, recommend it to be disabled + if ( !defined $enginestats{'InnoDB'} + && defined $myvar{'have_innodb'} + && $myvar{'have_innodb'} eq "YES" ) + { + badprint "InnoDB is enabled but isn't being used"; + push( @generalrec, + "Add skip-innodb to MySQL configuration to disable InnoDB" ); + } + if ( !defined $enginestats{'BerkeleyDB'} + && defined $myvar{'have_bdb'} + && $myvar{'have_bdb'} eq "YES" ) + { + badprint "BDB is enabled but isn't being used"; + push( @generalrec, + "Add skip-bdb to MySQL configuration to disable BDB" ); + } + if ( !defined $enginestats{'ISAM'} + && defined $myvar{'have_isam'} + && $myvar{'have_isam'} eq "YES" ) + { + badprint "MYISAM is enabled but isn't being used"; + push( @generalrec, +"Add skip-isam to MySQL configuration to disable ISAM (MySQL > 4.1.0)" + ); + } + + # Fragmented tables + if ( $fragtables > 0 ) { + badprint "Total fragmented tables: $fragtables"; + push( @generalrec, + "Run OPTIMIZE TABLE to defragment tables for better performance" ); + my $total_free = 0; + foreach my $table_line ( @{ $result{'Tables'}{'Fragmented tables'} } ) { + my ( $full_table_name, $data_free ) = split( /\s+/, $table_line ); + $data_free = 0 if ( !defined($data_free) or $data_free eq '' ); + $data_free = $data_free / 1024 / 1024; + $total_free += $data_free; + my ( $table_schema, $table_name ) = split( /\./, $full_table_name ); + push( @generalrec, +" OPTIMIZE TABLE `$table_schema`.`$table_name`; -- can free $data_free MB" + ); + } + push( @generalrec, + "Total freed space after theses OPTIMIZE TABLE : $total_free Mb" ); + } + else { + goodprint "Total fragmented tables: $fragtables"; + } + + # Auto increments + my %tblist; + + # Find the maximum integer + my $maxint = select_one "SELECT ~0"; + $result{'MaxInt'} = $maxint; + +# Now we use a database list, and loop through it to get storage engine stats for tables + foreach my $db (@dblist) { + chomp($db); + + if ( !$tblist{$db} ) { + $tblist{$db} = (); + } + + if ( $db eq "information_schema" ) { next; } + my @ia = ( 0, 10 ); + if ( !mysql_version_ge( 4, 1 ) ) { + + # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column + @ia = ( 0, 9 ); + } + push( + @{ $tblist{$db} }, + map { [ (split)[@ia] ] } + select_array "SHOW TABLE STATUS FROM \\\`$db\\\`" + ); + } + + my @dbnames = keys %tblist; + + foreach my $db (@dbnames) { + foreach my $tbl ( @{ $tblist{$db} } ) { + my ( $name, $autoincrement ) = @$tbl; + + if ( $autoincrement =~ /^\d+?$/ ) { + my $percent = percentage( $autoincrement, $maxint ); + $result{'PctAutoIncrement'}{"$db.$name"} = $percent; + if ( $percent >= 75 ) { + badprint +"Table '$db.$name' has an autoincrement value near max capacity ($percent%)"; + } + } + } + } + +} + +my %mycalc; + +sub calculations { + if ( $mystat{'Questions'} < 1 ) { + badprint + "Your server has not answered any queries - cannot continue..."; + exit 2; + } + + # Per-thread memory + if ( mysql_version_ge(4) ) { + $mycalc{'per_thread_buffers'} = + $myvar{'read_buffer_size'} + + $myvar{'read_rnd_buffer_size'} + + $myvar{'sort_buffer_size'} + + $myvar{'thread_stack'} + + $myvar{'max_allowed_packet'} + + $myvar{'join_buffer_size'}; + } + else { + $mycalc{'per_thread_buffers'} = + $myvar{'record_buffer'} + + $myvar{'record_rnd_buffer'} + + $myvar{'sort_buffer'} + + $myvar{'thread_stack'} + + $myvar{'join_buffer_size'}; + } + $mycalc{'total_per_thread_buffers'} = + $mycalc{'per_thread_buffers'} * $myvar{'max_connections'}; + $mycalc{'max_total_per_thread_buffers'} = + $mycalc{'per_thread_buffers'} * $mystat{'Max_used_connections'}; + + # Server-wide memory + $mycalc{'max_tmp_table_size'} = + ( $myvar{'tmp_table_size'} > $myvar{'max_heap_table_size'} ) + ? $myvar{'max_heap_table_size'} + : $myvar{'tmp_table_size'}; + $mycalc{'server_buffers'} = + $myvar{'key_buffer_size'} + $mycalc{'max_tmp_table_size'}; + $mycalc{'server_buffers'} += + ( defined $myvar{'innodb_buffer_pool_size'} ) + ? $myvar{'innodb_buffer_pool_size'} + : 0; + $mycalc{'server_buffers'} += + ( defined $myvar{'innodb_additional_mem_pool_size'} ) + ? $myvar{'innodb_additional_mem_pool_size'} + : 0; + $mycalc{'server_buffers'} += + ( defined $myvar{'innodb_log_buffer_size'} ) + ? $myvar{'innodb_log_buffer_size'} + : 0; + $mycalc{'server_buffers'} += + ( defined $myvar{'query_cache_size'} ) ? $myvar{'query_cache_size'} : 0; + $mycalc{'server_buffers'} += + ( defined $myvar{'aria_pagecache_buffer_size'} ) + ? $myvar{'aria_pagecache_buffer_size'} + : 0; + +# Global memory +# Max used memory is memory used by MySQL based on Max_used_connections +# This is the max memory used theoretically calculated with the max concurrent connection number reached by mysql + $mycalc{'max_used_memory'} = + $mycalc{'server_buffers'} + + $mycalc{"max_total_per_thread_buffers"} + + get_pf_memory(); + + # + get_gcache_memory(); + $mycalc{'pct_max_used_memory'} = + percentage( $mycalc{'max_used_memory'}, $physical_memory ); + +# Total possible memory is memory needed by MySQL based on max_connections +# This is the max memory MySQL can theoretically used if all connections allowed has opened by mysql + $mycalc{'max_peak_memory'} = + $mycalc{'server_buffers'} + + $mycalc{'total_per_thread_buffers'} + + get_pf_memory(); + + # + get_gcache_memory(); + $mycalc{'pct_max_physical_memory'} = + percentage( $mycalc{'max_peak_memory'}, $physical_memory ); + + debugprint "Max Used Memory: " + . hr_bytes( $mycalc{'max_used_memory'} ) . ""; + debugprint "Max Used Percentage RAM: " + . $mycalc{'pct_max_used_memory'} . "%"; + + debugprint "Max Peak Memory: " + . hr_bytes( $mycalc{'max_peak_memory'} ) . ""; + debugprint "Max Peak Percentage RAM: " + . $mycalc{'pct_max_physical_memory'} . "%"; + + # Slow queries + $mycalc{'pct_slow_queries'} = + int( ( $mystat{'Slow_queries'} / $mystat{'Questions'} ) * 100 ); + + # Connections + $mycalc{'pct_connections_used'} = int( + ( $mystat{'Max_used_connections'} / $myvar{'max_connections'} ) * 100 ); + $mycalc{'pct_connections_used'} = + ( $mycalc{'pct_connections_used'} > 100 ) + ? 100 + : $mycalc{'pct_connections_used'}; + + # Aborted Connections + $mycalc{'pct_connections_aborted'} = + percentage( $mystat{'Aborted_connects'}, $mystat{'Connections'} ); + debugprint "Aborted_connects: " . $mystat{'Aborted_connects'} . ""; + debugprint "Connections: " . $mystat{'Connections'} . ""; + debugprint "pct_connections_aborted: " + . $mycalc{'pct_connections_aborted'} . ""; + + # Key buffers + if ( mysql_version_ge( 4, 1 ) && $myvar{'key_buffer_size'} > 0 ) { + $mycalc{'pct_key_buffer_used'} = sprintf( + "%.1f", + ( + 1 - ( + ( + $mystat{'Key_blocks_unused'} * + $myvar{'key_cache_block_size'} + ) / $myvar{'key_buffer_size'} + ) + ) * 100 + ); + } + else { + $mycalc{'pct_key_buffer_used'} = 0; + } + + if ( $mystat{'Key_read_requests'} > 0 ) { + $mycalc{'pct_keys_from_mem'} = sprintf( + "%.1f", + ( + 100 - ( + ( $mystat{'Key_reads'} / $mystat{'Key_read_requests'} ) * + 100 + ) + ) + ); + } + else { + $mycalc{'pct_keys_from_mem'} = 0; + } + if ( defined $mystat{'Aria_pagecache_read_requests'} + && $mystat{'Aria_pagecache_read_requests'} > 0 ) + { + $mycalc{'pct_aria_keys_from_mem'} = sprintf( + "%.1f", + ( + 100 - ( + ( + $mystat{'Aria_pagecache_reads'} / + $mystat{'Aria_pagecache_read_requests'} + ) * 100 + ) + ) + ); + } + else { + $mycalc{'pct_aria_keys_from_mem'} = 0; + } + + if ( $mystat{'Key_write_requests'} > 0 ) { + $mycalc{'pct_wkeys_from_mem'} = sprintf( "%.1f", + ( ( $mystat{'Key_writes'} / $mystat{'Key_write_requests'} ) * 100 ) + ); + } + else { + $mycalc{'pct_wkeys_from_mem'} = 0; + } + + if ( $doremote eq 0 and !mysql_version_ge(5) ) { + my $size = 0; + $size += (split)[0] + for +`find "$myvar{'datadir'}" -name "*.MYI" -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1`; + $mycalc{'total_myisam_indexes'} = $size; + $size = 0 + (split)[0] + for +`find "$myvar{'datadir'}" -name "*.MAI" -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1`; + $mycalc{'total_aria_indexes'} = $size; + } + elsif ( mysql_version_ge(5) ) { + $mycalc{'total_myisam_indexes'} = select_one +"SELECT IFNULL(SUM(INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';"; + $mycalc{'total_aria_indexes'} = select_one +"SELECT IFNULL(SUM(INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'Aria';"; + } + if ( defined $mycalc{'total_myisam_indexes'} ) { + chomp( $mycalc{'total_myisam_indexes'} ); + } + if ( defined $mycalc{'total_aria_indexes'} ) { + chomp( $mycalc{'total_aria_indexes'} ); + } + + # Query cache + if ( mysql_version_ge(8) and mysql_version_le(10) ) { + $mycalc{'query_cache_efficiency'} = 0; + } + elsif ( mysql_version_ge(4) ) { + $mycalc{'query_cache_efficiency'} = sprintf( + "%.1f", + ( + $mystat{'Qcache_hits'} / + ( $mystat{'Com_select'} + $mystat{'Qcache_hits'} ) + ) * 100 + ); + if ( $myvar{'query_cache_size'} ) { + $mycalc{'pct_query_cache_used'} = sprintf( + "%.1f", + 100 - ( + $mystat{'Qcache_free_memory'} / $myvar{'query_cache_size'} + ) * 100 + ); + } + if ( $mystat{'Qcache_lowmem_prunes'} == 0 ) { + $mycalc{'query_cache_prunes_per_day'} = 0; + } + else { + $mycalc{'query_cache_prunes_per_day'} = int( + $mystat{'Qcache_lowmem_prunes'} / ( $mystat{'Uptime'} / 86400 ) + ); + } + } + + # Sorting + $mycalc{'total_sorts'} = $mystat{'Sort_scan'} + $mystat{'Sort_range'}; + if ( $mycalc{'total_sorts'} > 0 ) { + $mycalc{'pct_temp_sort_table'} = int( + ( $mystat{'Sort_merge_passes'} / $mycalc{'total_sorts'} ) * 100 ); + } + + # Joins + $mycalc{'joins_without_indexes'} = + $mystat{'Select_range_check'} + $mystat{'Select_full_join'}; + $mycalc{'joins_without_indexes_per_day'} = + int( $mycalc{'joins_without_indexes'} / ( $mystat{'Uptime'} / 86400 ) ); + + # Temporary tables + if ( $mystat{'Created_tmp_tables'} > 0 ) { + if ( $mystat{'Created_tmp_disk_tables'} > 0 ) { + $mycalc{'pct_temp_disk'} = int( + ( + $mystat{'Created_tmp_disk_tables'} / + $mystat{'Created_tmp_tables'} + ) * 100 + ); + } + else { + $mycalc{'pct_temp_disk'} = 0; + } + } + + # Table cache + if ( $mystat{'Opened_tables'} > 0 ) { + if ( not defined( $mystat{'Table_open_cache_hits'} ) ) { + $mycalc{'table_cache_hit_rate'} = + int( $mystat{'Open_tables'} * 100 / $mystat{'Opened_tables'} ); + } + else { + $mycalc{'table_cache_hit_rate'} = int( + $mystat{'Table_open_cache_hits'} * 100 / ( + $mystat{'Table_open_cache_hits'} + + $mystat{'Table_open_cache_misses'} + ) + ); + } + + } + else { + $mycalc{'table_cache_hit_rate'} = 100; + } + + # Open files + if ( $myvar{'open_files_limit'} > 0 ) { + $mycalc{'pct_files_open'} = + int( $mystat{'Open_files'} * 100 / $myvar{'open_files_limit'} ); + } + + # Table locks + if ( $mystat{'Table_locks_immediate'} > 0 ) { + if ( $mystat{'Table_locks_waited'} == 0 ) { + $mycalc{'pct_table_locks_immediate'} = 100; + } + else { + $mycalc{'pct_table_locks_immediate'} = int( + $mystat{'Table_locks_immediate'} * 100 / ( + $mystat{'Table_locks_waited'} + + $mystat{'Table_locks_immediate'} + ) + ); + } + } + + # Thread cache + $mycalc{'thread_cache_hit_rate'} = + int( 100 - + ( ( $mystat{'Threads_created'} / $mystat{'Connections'} ) * 100 ) ); + + # Other + if ( $mystat{'Connections'} > 0 ) { + $mycalc{'pct_aborted_connections'} = + int( ( $mystat{'Aborted_connects'} / $mystat{'Connections'} ) * 100 ); + } + if ( $mystat{'Questions'} > 0 ) { + $mycalc{'total_reads'} = $mystat{'Com_select'}; + $mycalc{'total_writes'} = + $mystat{'Com_delete'} + + $mystat{'Com_insert'} + + $mystat{'Com_update'} + + $mystat{'Com_replace'}; + if ( $mycalc{'total_reads'} == 0 ) { + $mycalc{'pct_reads'} = 0; + $mycalc{'pct_writes'} = 100; + } + else { + $mycalc{'pct_reads'} = int( + ( + $mycalc{'total_reads'} / + ( $mycalc{'total_reads'} + $mycalc{'total_writes'} ) + ) * 100 + ); + $mycalc{'pct_writes'} = 100 - $mycalc{'pct_reads'}; + } + } + + # InnoDB + $myvar{'innodb_log_files_in_group'} = 1 + unless defined( $myvar{'innodb_log_files_in_group'} ); + $myvar{"innodb_buffer_pool_instances"} = 1 + unless defined( $myvar{'innodb_buffer_pool_instances'} ); + if ( $myvar{'have_innodb'} eq "YES" ) { + $mycalc{'innodb_log_size_pct'} = + ( $myvar{'innodb_log_file_size'} * + $myvar{'innodb_log_files_in_group'} * 100 / + $myvar{'innodb_buffer_pool_size'} ); + } + if ( !defined $myvar{'innodb_buffer_pool_size'} ) { + $mycalc{'innodb_log_size_pct'} = 0; + $myvar{'innodb_buffer_pool_size'} = 0; + } + + # InnoDB Buffer pool read cache efficiency + ( + $mystat{'Innodb_buffer_pool_read_requests'}, + $mystat{'Innodb_buffer_pool_reads'} + ) + = ( 1, 1 ) + unless defined $mystat{'Innodb_buffer_pool_reads'}; + $mycalc{'pct_read_efficiency'} = percentage( + ( + $mystat{'Innodb_buffer_pool_read_requests'} - + $mystat{'Innodb_buffer_pool_reads'} + ), + $mystat{'Innodb_buffer_pool_read_requests'} + ) if defined $mystat{'Innodb_buffer_pool_read_requests'}; + debugprint "pct_read_efficiency: " . $mycalc{'pct_read_efficiency'} . ""; + debugprint "Innodb_buffer_pool_reads: " + . $mystat{'Innodb_buffer_pool_reads'} . ""; + debugprint "Innodb_buffer_pool_read_requests: " + . $mystat{'Innodb_buffer_pool_read_requests'} . ""; + + # InnoDB log write cache efficiency + ( $mystat{'Innodb_log_write_requests'}, $mystat{'Innodb_log_writes'} ) = + ( 1, 1 ) + unless defined $mystat{'Innodb_log_writes'}; + $mycalc{'pct_write_efficiency'} = percentage( + ( $mystat{'Innodb_log_write_requests'} - $mystat{'Innodb_log_writes'} ), + $mystat{'Innodb_log_write_requests'} + ) if defined $mystat{'Innodb_log_write_requests'}; + debugprint "pct_write_efficiency: " . $mycalc{'pct_write_efficiency'} . ""; + debugprint "Innodb_log_writes: " . $mystat{'Innodb_log_writes'} . ""; + debugprint "Innodb_log_write_requests: " + . $mystat{'Innodb_log_write_requests'} . ""; + $mycalc{'pct_innodb_buffer_used'} = percentage( + ( + $mystat{'Innodb_buffer_pool_pages_total'} - + $mystat{'Innodb_buffer_pool_pages_free'} + ), + $mystat{'Innodb_buffer_pool_pages_total'} + ) if defined $mystat{'Innodb_buffer_pool_pages_total'}; + + # Binlog Cache + if ( $myvar{'log_bin'} ne 'OFF' ) { + $mycalc{'pct_binlog_cache'} = percentage( + $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'}, + $mystat{'Binlog_cache_use'} ); + } +} + +sub mysql_stats { + subheaderprint "Performance Metrics"; + + # Show uptime, queries per second, connections, traffic stats + my $qps; + if ( $mystat{'Uptime'} > 0 ) { + $qps = sprintf( "%.3f", $mystat{'Questions'} / $mystat{'Uptime'} ); + } + push( @generalrec, +"MySQL was started within the last 24 hours - recommendations may be inaccurate" + ) if ( $mystat{'Uptime'} < 86400 ); + infoprint "Up for: " + . pretty_uptime( $mystat{'Uptime'} ) . " (" + . hr_num( $mystat{'Questions'} ) . " q [" + . hr_num($qps) + . " qps], " + . hr_num( $mystat{'Connections'} ) + . " conn," . " TX: " + . hr_bytes_rnd( $mystat{'Bytes_sent'} ) + . ", RX: " + . hr_bytes_rnd( $mystat{'Bytes_received'} ) . ")"; + infoprint "Reads / Writes: " + . $mycalc{'pct_reads'} . "% / " + . $mycalc{'pct_writes'} . "%"; + + # Binlog Cache + if ( $myvar{'log_bin'} eq 'OFF' ) { + infoprint "Binary logging is disabled"; + } + else { + infoprint "Binary logging is enabled (GTID MODE: " + . ( defined( $myvar{'gtid_mode'} ) ? $myvar{'gtid_mode'} : "OFF" ) + . ")"; + } + + # Memory usage + infoprint "Physical Memory : " . hr_bytes($physical_memory); + infoprint "Max MySQL memory : " . hr_bytes( $mycalc{'max_peak_memory'} ); + infoprint "Other process memory: " . hr_bytes( get_other_process_memory() ); + + infoprint "Total buffers: " + . hr_bytes( $mycalc{'server_buffers'} ) + . " global + " + . hr_bytes( $mycalc{'per_thread_buffers'} ) + . " per thread ($myvar{'max_connections'} max threads)"; + infoprint "P_S Max memory usage: " . hr_bytes_rnd( get_pf_memory() ); + $result{'P_S'}{'memory'} = get_pf_memory(); + $result{'P_S'}{'pretty_memory'} = + hr_bytes_rnd( get_pf_memory() ); + infoprint "Galera GCache Max memory usage: " + . hr_bytes_rnd( get_gcache_memory() ); + $result{'Galera'}{'GCache'}{'memory'} = get_gcache_memory(); + $result{'Galera'}{'GCache'}{'pretty_memory'} = + hr_bytes_rnd( get_gcache_memory() ); + + if ( $opt{buffers} ne 0 ) { + infoprint "Global Buffers"; + infoprint " +-- Key Buffer: " + . hr_bytes( $myvar{'key_buffer_size'} ) . ""; + infoprint " +-- Max Tmp Table: " + . hr_bytes( $mycalc{'max_tmp_table_size'} ) . ""; + + if ( defined $myvar{'query_cache_type'} ) { + infoprint "Query Cache Buffers"; + infoprint " +-- Query Cache: " + . $myvar{'query_cache_type'} . " - " + . ( + $myvar{'query_cache_type'} eq 0 | + $myvar{'query_cache_type'} eq 'OFF' ? "DISABLED" + : ( + $myvar{'query_cache_type'} eq 1 ? "ALL REQUESTS" + : "ON DEMAND" + ) + ) . ""; + infoprint " +-- Query Cache Size: " + . hr_bytes( $myvar{'query_cache_size'} ) . ""; + } + + infoprint "Per Thread Buffers"; + infoprint " +-- Read Buffer: " + . hr_bytes( $myvar{'read_buffer_size'} ) . ""; + infoprint " +-- Read RND Buffer: " + . hr_bytes( $myvar{'read_rnd_buffer_size'} ) . ""; + infoprint " +-- Sort Buffer: " + . hr_bytes( $myvar{'sort_buffer_size'} ) . ""; + infoprint " +-- Thread stack: " + . hr_bytes( $myvar{'thread_stack'} ) . ""; + infoprint " +-- Join Buffer: " + . hr_bytes( $myvar{'join_buffer_size'} ) . ""; + if ( $myvar{'log_bin'} ne 'OFF' ) { + infoprint "Binlog Cache Buffers"; + infoprint " +-- Binlog Cache: " + . hr_bytes( $myvar{'binlog_cache_size'} ) . ""; + } + } + + if ( $arch + && $arch == 32 + && $mycalc{'max_used_memory'} > 2 * 1024 * 1024 * 1024 ) + { + badprint + "Allocating > 2GB RAM on 32-bit systems can cause system instability"; + badprint "Maximum reached memory usage: " + . hr_bytes( $mycalc{'max_used_memory'} ) + . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; + } + elsif ( $mycalc{'pct_max_used_memory'} > 85 ) { + badprint "Maximum reached memory usage: " + . hr_bytes( $mycalc{'max_used_memory'} ) + . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; + } + else { + goodprint "Maximum reached memory usage: " + . hr_bytes( $mycalc{'max_used_memory'} ) + . " ($mycalc{'pct_max_used_memory'}% of installed RAM)"; + } + + if ( $mycalc{'pct_max_physical_memory'} > 85 ) { + badprint "Maximum possible memory usage: " + . hr_bytes( $mycalc{'max_peak_memory'} ) + . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)"; + push( @generalrec, + "Reduce your overall MySQL memory footprint for system stability" ); + } + else { + goodprint "Maximum possible memory usage: " + . hr_bytes( $mycalc{'max_peak_memory'} ) + . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)"; + } + + if ( $physical_memory < + ( $mycalc{'max_peak_memory'} + get_other_process_memory() ) ) + { + badprint + "Overall possible memory usage with other process exceeded memory"; + push( @generalrec, + "Dedicate this server to your database for highest performance." ); + } + else { + goodprint +"Overall possible memory usage with other process is compatible with memory available"; + } + + # Slow queries + if ( $mycalc{'pct_slow_queries'} > 5 ) { + badprint "Slow queries: $mycalc{'pct_slow_queries'}% (" + . hr_num( $mystat{'Slow_queries'} ) . "/" + . hr_num( $mystat{'Questions'} ) . ")"; + } + else { + goodprint "Slow queries: $mycalc{'pct_slow_queries'}% (" + . hr_num( $mystat{'Slow_queries'} ) . "/" + . hr_num( $mystat{'Questions'} ) . ")"; + } + if ( $myvar{'long_query_time'} > 10 ) { + push( @adjvars, "long_query_time (<= 10)" ); + } + if ( defined( $myvar{'log_slow_queries'} ) ) { + if ( $myvar{'log_slow_queries'} eq "OFF" ) { + push( @generalrec, + "Enable the slow query log to troubleshoot bad queries" ); + } + } + + # Connections + if ( $mycalc{'pct_connections_used'} > 85 ) { + badprint +"Highest connection usage: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; + push( @adjvars, + "max_connections (> " . $myvar{'max_connections'} . ")" ); + push( @adjvars, + "wait_timeout (< " . $myvar{'wait_timeout'} . ")", + "interactive_timeout (< " . $myvar{'interactive_timeout'} . ")" ); + push( @generalrec, +"Reduce or eliminate persistent connections to reduce connection usage" + ); + } + else { + goodprint +"Highest usage of available connections: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; + } + + # Aborted Connections + if ( $mycalc{'pct_connections_aborted'} > 3 ) { + badprint +"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; + push( @generalrec, + "Reduce or eliminate unclosed connections and network issues" ); + } + else { + goodprint +"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; + } + + # name resolution + if ( defined( $result{'Variables'}{'skip_networking'} ) + && $result{'Variables'}{'skip_networking'} eq 'ON' ) + { + infoprint +"Skipped name resolution test due to skip_networking=ON in system variables."; + } + elsif ( not defined( $result{'Variables'}{'skip_name_resolve'} ) ) { + infoprint +"Skipped name resolution test due to missing skip_name_resolve in system variables."; + } + #Cpanel and Skip name resolve + elsif ( -r "/usr/local/cpanel/cpanel" ){ + if ( $result{'Variables'}{'skip_name_resolve'} ne 'OFF') { + infoprint "CPanel and Flex system skip-name-resolve should be on"; + } + if ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF') { + badprint "CPanel and Flex system skip-name-resolve should be on"; + push (@generalrec, "name resolution is enabled due to cPanel doesn't support this disabled."); + push (@adjvars, "skip-name-resolve=0"); + } + } + elsif ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF' ) { + badprint +"Name resolution is active: a reverse name resolution is made for each new connection and can reduce performance"; + push( @generalrec, +"Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=1" + ); + push (@adjvars, "skip-name-resolve=1"); + } + + # Query cache + if ( !mysql_version_ge(4) ) { + + # MySQL versions < 4.01 don't support query caching + push( @generalrec, + "Upgrade MySQL to version 4+ to utilize query caching" ); + } + elsif ( mysql_version_eq(8) ) { + infoprint "Query cache have been removed in MySQL 8"; + + #return; + } + elsif ($myvar{'query_cache_size'} < 1 + or $myvar{'query_cache_type'} eq "OFF" ) + { + goodprint +"Query cache is disabled by default due to mutex contention on multiprocessor machines."; + } + elsif ( $mystat{'Com_select'} == 0 ) { + badprint + "Query cache cannot be analyzed - no SELECT statements executed"; + } + else { + badprint + "Query cache may be disabled by default due to mutex contention."; + push( @adjvars, "query_cache_size (=0)" ); + push( @adjvars, "query_cache_type (=0)" ); + if ( $mycalc{'query_cache_efficiency'} < 20 ) { + badprint + "Query cache efficiency: $mycalc{'query_cache_efficiency'}% (" + . hr_num( $mystat{'Qcache_hits'} ) + . " cached / " + . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} ) + . " selects)"; + push( @adjvars, + "query_cache_limit (> " + . hr_bytes_rnd( $myvar{'query_cache_limit'} ) + . ", or use smaller result sets)" ); + } + else { + goodprint + "Query cache efficiency: $mycalc{'query_cache_efficiency'}% (" + . hr_num( $mystat{'Qcache_hits'} ) + . " cached / " + . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} ) + . " selects)"; + } + if ( $mycalc{'query_cache_prunes_per_day'} > 98 ) { + badprint +"Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; + if ( $myvar{'query_cache_size'} >= 128 * 1024 * 1024 ) { + push( @generalrec, +"Increasing the query_cache size over 128M may reduce performance" + ); + push( @adjvars, + "query_cache_size (> " + . hr_bytes_rnd( $myvar{'query_cache_size'} ) + . ") [see warning above]" ); + } + else { + push( @adjvars, + "query_cache_size (> " + . hr_bytes_rnd( $myvar{'query_cache_size'} ) + . ")" ); + } + } + else { + goodprint +"Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; + } + } + + # Sorting + if ( $mycalc{'total_sorts'} == 0 ) { + goodprint "No Sort requiring temporary tables"; + } + elsif ( $mycalc{'pct_temp_sort_table'} > 10 ) { + badprint + "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}% (" + . hr_num( $mystat{'Sort_merge_passes'} ) + . " temp sorts / " + . hr_num( $mycalc{'total_sorts'} ) + . " sorts)"; + push( @adjvars, + "sort_buffer_size (> " + . hr_bytes_rnd( $myvar{'sort_buffer_size'} ) + . ")" ); + push( @adjvars, + "read_rnd_buffer_size (> " + . hr_bytes_rnd( $myvar{'read_rnd_buffer_size'} ) + . ")" ); + } + else { + goodprint + "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}% (" + . hr_num( $mystat{'Sort_merge_passes'} ) + . " temp sorts / " + . hr_num( $mycalc{'total_sorts'} ) + . " sorts)"; + } + + # Joins + if ( $mycalc{'joins_without_indexes_per_day'} > 250 ) { + badprint + "Joins performed without indexes: $mycalc{'joins_without_indexes'}"; + push( @adjvars, + "join_buffer_size (> " + . hr_bytes( $myvar{'join_buffer_size'} ) + . ", or always use indexes with JOINs)" ); + push( + @generalrec, +"We will suggest raising the 'join_buffer_size' until JOINs not using indexes are found. + See https://dev.mysql.com/doc/internals/en/join-buffer-size.html + (specially the conclusions at the bottom of the page)." + ); + } + else { + goodprint "No joins without indexes"; + + # No joins have run without indexes + } + + # Temporary tables + if ( $mystat{'Created_tmp_tables'} > 0 ) { + if ( $mycalc{'pct_temp_disk'} > 25 + && $mycalc{'max_tmp_table_size'} < 256 * 1024 * 1024 ) + { + badprint + "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" + . hr_num( $mystat{'Created_tmp_disk_tables'} ) + . " on disk / " + . hr_num( $mystat{'Created_tmp_tables'} ) + . " total)"; + push( @adjvars, + "tmp_table_size (> " + . hr_bytes_rnd( $myvar{'tmp_table_size'} ) + . ")" ); + push( @adjvars, + "max_heap_table_size (> " + . hr_bytes_rnd( $myvar{'max_heap_table_size'} ) + . ")" ); + push( @generalrec, +"When making adjustments, make tmp_table_size/max_heap_table_size equal" + ); + push( @generalrec, + "Reduce your SELECT DISTINCT queries which have no LIMIT clause" + ); + } + elsif ($mycalc{'pct_temp_disk'} > 25 + && $mycalc{'max_tmp_table_size'} >= 256 * 1024 * 1024 ) + { + badprint + "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" + . hr_num( $mystat{'Created_tmp_disk_tables'} ) + . " on disk / " + . hr_num( $mystat{'Created_tmp_tables'} ) + . " total)"; + push( @generalrec, + "Temporary table size is already large - reduce result set size" + ); + push( @generalrec, + "Reduce your SELECT DISTINCT queries without LIMIT clauses" ); + } + else { + goodprint + "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (" + . hr_num( $mystat{'Created_tmp_disk_tables'} ) + . " on disk / " + . hr_num( $mystat{'Created_tmp_tables'} ) + . " total)"; + } + } + else { + goodprint "No tmp tables created on disk"; + } + + # Thread cache + if ( defined( $myvar{'have_threadpool'} ) + and $myvar{'have_threadpool'} eq 'YES' ) + { +# https://www.percona.com/doc/percona-server/5.7/performance/threadpool.html#status-variables +# When thread pool is enabled, the value of the thread_cache_size variable +# is ignored. The Threads_cached status variable contains 0 in this case. + infoprint "Thread cache not used with thread pool enabled"; + } + else { + if ( $myvar{'thread_cache_size'} eq 0 ) { + badprint "Thread cache is disabled"; + push( @generalrec, + "Set thread_cache_size to 4 as a starting value" ); + push( @adjvars, "thread_cache_size (start at 4)" ); + } + else { + if ( $mycalc{'thread_cache_hit_rate'} <= 50 ) { + badprint + "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}% (" + . hr_num( $mystat{'Threads_created'} ) + . " created / " + . hr_num( $mystat{'Connections'} ) + . " connections)"; + push( @adjvars, + "thread_cache_size (> $myvar{'thread_cache_size'})" ); + } + else { + goodprint + "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}% (" + . hr_num( $mystat{'Threads_created'} ) + . " created / " + . hr_num( $mystat{'Connections'} ) + . " connections)"; + } + } + } + + # Table cache + my $table_cache_var = ""; + if ( $mystat{'Open_tables'} > 0 ) { + if ( $mycalc{'table_cache_hit_rate'} < 20 ) { + + unless ( defined( $mystat{'Table_open_cache_hits'} ) ) { + badprint + "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" + . hr_num( $mystat{'Open_tables'} ) + . " hits / " + . hr_num( $mystat{'Opened_tables'} ) + . " requests)"; + } + else { + badprint + "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" + . hr_num( $mystat{'Table_open_cache_hits'} ) + . " hits / " + . hr_num( $mystat{'Table_open_cache_hits'} + + $mystat{'Table_open_cache_misses'} ) + . " requests)"; + } + + if ( mysql_version_ge( 5, 1 ) ) { + $table_cache_var = "table_open_cache"; + } + else { + $table_cache_var = "table_cache"; + } + + push( @adjvars, + $table_cache_var . " (> " . $myvar{$table_cache_var} . ")" ); + push( @generalrec, + "Increase " + . $table_cache_var + . " gradually to avoid file descriptor limits" ); + push( @generalrec, + "Read this before increasing " + . $table_cache_var + . " over 64: https://bit.ly/2Fulv7r" ); + push( @generalrec, + "Read this before increasing for MariaDB" + . " https://mariadb.com/kb/en/library/optimizing-table_open_cache/" + ); + push( @generalrec, +"This is MyISAM only table_cache scalability problem, InnoDB not affected." + ); + push( @generalrec, + "See more details here: https://bugs.mysql.com/bug.php?id=49177" + ); + push( @generalrec, +"This bug already fixed in MySQL 5.7.9 and newer MySQL versions." + ); + push( @generalrec, + "Beware that open_files_limit (" + . $myvar{'open_files_limit'} + . ") variable " ); + push( @generalrec, + "should be greater than $table_cache_var (" + . $myvar{$table_cache_var} + . ")" ); + } + else { + unless ( defined( $mystat{'Table_open_cache_hits'} ) ) { + goodprint + "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" + . hr_num( $mystat{'Open_tables'} ) + . " hits / " + . hr_num( $mystat{'Opened_tables'} ) + . " requests)"; + } + else { + goodprint + "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" + . hr_num( $mystat{'Table_open_cache_hits'} ) + . " hits / " + . hr_num( $mystat{'Table_open_cache_hits'} + + $mystat{'Table_open_cache_misses'} ) + . " requests)"; + } + } + } + + # Table definition cache + my $nbtables = select_one('SELECT COUNT(*) FROM information_schema.tables'); + $mycalc{'total_tables'} = $nbtables; + if ( defined $myvar{'table_definition_cache'} ) { + if ( $myvar{'table_definition_cache'} == -1 ) { + infoprint( "table_definition_cache(" + . $myvar{'table_definition_cache'} + . ") is in autosizing mode" ); + } + elsif ( $myvar{'table_definition_cache'} < $nbtables ) { + badprint "table_definition_cache (" + . $myvar{'table_definition_cache'} + . ") is less than number of tables ($nbtables) "; + push( @adjvars, + "table_definition_cache(" + . $myvar{'table_definition_cache'} . ") > " + . $nbtables + . " or -1 (autosizing if supported)" ); + } + else { + goodprint "table_definition_cache (" + . $myvar{'table_definition_cache'} + . ") is greater than number of tables ($nbtables)"; + } + } + else { + infoprint "No table_definition_cache variable found."; + } + + # Open files + if ( defined $mycalc{'pct_files_open'} ) { + if ( $mycalc{'pct_files_open'} > 85 ) { + badprint "Open file limit used: $mycalc{'pct_files_open'}% (" + . hr_num( $mystat{'Open_files'} ) . "/" + . hr_num( $myvar{'open_files_limit'} ) . ")"; + push( @adjvars, + "open_files_limit (> " . $myvar{'open_files_limit'} . ")" ); + } + else { + goodprint "Open file limit used: $mycalc{'pct_files_open'}% (" + . hr_num( $mystat{'Open_files'} ) . "/" + . hr_num( $myvar{'open_files_limit'} ) . ")"; + } + } + + # Table locks + if ( defined $mycalc{'pct_table_locks_immediate'} ) { + if ( $mycalc{'pct_table_locks_immediate'} < 95 ) { + badprint +"Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}%"; + push( @generalrec, + "Optimize queries and/or use InnoDB to reduce lock wait" ); + } + else { + goodprint +"Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}% (" + . hr_num( $mystat{'Table_locks_immediate'} ) + . " immediate / " + . hr_num( $mystat{'Table_locks_waited'} + + $mystat{'Table_locks_immediate'} ) + . " locks)"; + } + } + + # Binlog cache + if ( defined $mycalc{'pct_binlog_cache'} ) { + if ( $mycalc{'pct_binlog_cache'} < 90 + && $mystat{'Binlog_cache_use'} > 0 ) + { + badprint "Binlog cache memory access: " + . $mycalc{'pct_binlog_cache'} . "% (" + . ( + $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} ) + . " Memory / " + . $mystat{'Binlog_cache_use'} + . " Total)"; + push( @generalrec, + "Increase binlog_cache_size (Actual value: " + . $myvar{'binlog_cache_size'} + . ")" ); + push( @adjvars, + "binlog_cache_size (" + . hr_bytes( $myvar{'binlog_cache_size'} + 16 * 1024 * 1024 ) + . ")" ); + } + else { + goodprint "Binlog cache memory access: " + . $mycalc{'pct_binlog_cache'} . "% (" + . ( + $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} ) + . " Memory / " + . $mystat{'Binlog_cache_use'} + . " Total)"; + debugprint "Not enough data to validate binlog cache size\n" + if $mystat{'Binlog_cache_use'} < 10; + } + } + + # Performance options + if ( !mysql_version_ge( 5, 1 ) ) { + push( @generalrec, "Upgrade to MySQL 5.5+ to use asynchronous write" ); + } + elsif ( $myvar{'concurrent_insert'} eq "OFF" ) { + push( @generalrec, "Enable concurrent_insert by setting it to 'ON'" ); + } + elsif ( $myvar{'concurrent_insert'} eq 0 ) { + push( @generalrec, "Enable concurrent_insert by setting it to 1" ); + } +} + +# Recommendations for MyISAM +sub mysql_myisam { + subheaderprint "MyISAM Metrics"; + if ( mysql_version_ge(8) and mysql_version_le(10) ) { + infoprint "MyISAM Metrics are disabled on last MySQL versions."; + if ( $myvar{'key_buffer_size'} > 0) { + push( @adjvars, "key_buffer_size=0" ); + push( @generalrec, "Buffer Key MyISAM set to 0, no MyISAM table detected" ); + } + return; + } + my $nb_myisam_tables=select_one( +"SELECT COUNT(*) FROM information_schema.TABLES WHERE ENGINE='MyISAM'" + ); + if ( $nb_myisam_tables == 0 ) { + infoprint "No MyISAM table(s) detected ...."; + return; + } + + # Key buffer usage + if ( $mycalc{'pct_key_buffer_used'} > 0 ) { + if ( $mycalc{'pct_key_buffer_used'} < 90 ) { + badprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * + $myvar{'key_cache_block_size'} ) + . " used / " + . hr_bytes( $myvar{'key_buffer_size'} ) + . " cache)"; + + push( + @adjvars, + "key_buffer_size (\~ " + . hr_num( + $myvar{'key_buffer_size'} * $mycalc{'pct_key_buffer_used'} + / 100 + ) + . ")" + ); + } + else { + goodprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * + $myvar{'key_cache_block_size'} ) + . " used / " + . hr_bytes( $myvar{'key_buffer_size'} ) + . " cache)"; + } + } + else { + + # No queries have run that would use keys + debugprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) + . " used / " + . hr_bytes( $myvar{'key_buffer_size'} ) + . " cache)"; + } + + # Key buffer + if ( !defined( $mycalc{'total_myisam_indexes'} ) ) { + push( @generalrec, + "Unable to calculate MyISAM index size on MySQL server < 5.0.0" ); + } + else { + if ( $myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'} + && $mycalc{'pct_keys_from_mem'} < 95 ) + { + badprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + push( @adjvars, + "key_buffer_size (> " + . hr_bytes( $mycalc{'total_myisam_indexes'} ) + . ")" ); + } + else { + goodprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + } + if ( $mystat{'Key_read_requests'} > 0 ) { + if ( $mycalc{'pct_keys_from_mem'} < 95 ) { + badprint + "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" + . hr_num( $mystat{'Key_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_reads'} ) + . " reads)"; + } + else { + goodprint + "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" + . hr_num( $mystat{'Key_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_reads'} ) + . " reads)"; + } + } + else { + + # No queries have run that would use keys + debugprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + } + if ( $mystat{'Key_write_requests'} > 0 ) { + if ( $mycalc{'pct_wkeys_from_mem'} < 95 ) { + badprint + "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; + } + else { + goodprint + "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; + } + } + else { + + # No queries have run that would use keys + debugprint + "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; + } + } +} + +# Recommendations for ThreadPool +sub mariadb_threadpool { + subheaderprint "ThreadPool Metrics"; + + # MariaDB + unless ( defined $myvar{'have_threadpool'} + && $myvar{'have_threadpool'} eq "YES" ) + { + infoprint "ThreadPool stat is disabled."; + return; + } + infoprint "ThreadPool stat is enabled."; + infoprint "Thread Pool Size: " . $myvar{'thread_pool_size'} . " thread(s)."; + + if ( $myvar{'version'} =~ /percona/i + or $myvar{'version_comment'} =~ /percona/i ) + { + my $np = cpu_cores; + if ( $myvar{'thread_pool_size'} >= $np + and $myvar{'thread_pool_size'} < ( $np * 1.5 ) ) + { + goodprint +"thread_pool_size for Percona between 1 and 1.5 times number of CPUs (" + . $np . " and " + . ( $np * 1.5 ) . ")"; + } + else { + badprint +"thread_pool_size for Percona between 1 and 1.5 times number of CPUs (" + . $np . " and " + . ( $np * 1.5 ) . ")"; + push( @adjvars, + "thread_pool_size between " + . $np . " and " + . ( $np * 1.5 ) + . " for InnoDB usage" ); + } + return; + } + + if ( $myvar{'version'} =~ /mariadb/i ) { + infoprint "Using default value is good enough for your version (" + . $myvar{'version'} . ")"; + return; + } + + if ( $myvar{'have_innodb'} eq 'YES' ) { + if ( $myvar{'thread_pool_size'} < 16 + or $myvar{'thread_pool_size'} > 36 ) + { + badprint +"thread_pool_size between 16 and 36 when using InnoDB storage engine."; + push( @generalrec, + "Thread pool size for InnoDB usage (" + . $myvar{'thread_pool_size'} + . ")" ); + push( @adjvars, + "thread_pool_size between 16 and 36 for InnoDB usage" ); + } + else { + goodprint +"thread_pool_size between 16 and 36 when using InnoDB storage engine."; + } + return; + } + if ( $myvar{'have_isam'} eq 'YES' ) { + if ( $myvar{'thread_pool_size'} < 4 or $myvar{'thread_pool_size'} > 8 ) + { + badprint +"thread_pool_size between 4 and 8 when using MyIsam storage engine."; + push( @generalrec, + "Thread pool size for MyIsam usage (" + . $myvar{'thread_pool_size'} + . ")" ); + push( @adjvars, + "thread_pool_size between 4 and 8 for MyIsam usage" ); + } + else { + goodprint +"thread_pool_size between 4 and 8 when using MyISAM storage engine."; + } + } +} + +sub get_pf_memory { + + # Performance Schema + return 0 unless defined $myvar{'performance_schema'}; + return 0 if $myvar{'performance_schema'} eq 'OFF'; + + my @infoPFSMemory = grep /performance_schema.memory/, + select_array("SHOW ENGINE PERFORMANCE_SCHEMA STATUS"); + return 0 if scalar(@infoPFSMemory) == 0; + $infoPFSMemory[0] =~ s/.*\s+(\d+)$/$1/g; + return $infoPFSMemory[0]; +} + +# Recommendations for Performance Schema +sub mysqsl_pfs { + subheaderprint "Performance schema"; + + # Performance Schema + $myvar{'performance_schema'} = 'OFF' + unless defined( $myvar{'performance_schema'} ); + if ($myvar{'performance_schema'} eq 'OFF') { + badprint "Performance_schema should be activated."; + push( @adjvars, "performance_schema=ON" ); + push( @generalrec, + "Performance schema should be activated for better diagnostics" + ); + } + if ( $myvar{'performance_schema'} eq 'ON' ) { + infoprint "Performance_schema is activated."; + debugprint "Performance schema is " . $myvar{'performance_schema'}; + infoprint "Memory used by P_S: " . hr_bytes( get_pf_memory() ); + } + + unless ( grep /^sys$/, select_array("SHOW DATABASES") ) { + infoprint "Sys schema isn't installed."; + push( @generalrec, +"Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL" + ) unless ( mysql_version_le( 5, 6 ) ); + push( @generalrec, +"Consider installing Sys schema from https://github.com/FromDual/mariadb-sys for MariaDB" + ) unless ( mysql_version_ge( 10, 0 ) ); + + return; + } + infoprint "Sys schema is installed."; + return if ( $opt{pfstat} == 0 or $myvar{'performance_schema'} ne 'ON' ); + + infoprint "Sys schema Version: " + . select_one("select sys_version from sys.version"); + + # Store all sys schema +# for my $pfs_view(select_array('use sys;show tables;')){ + #infoprint "$pfs_view" +# @$result{'sys'}{$pfs_view}{'headers'}=[]; +# for my $h (select_array("select column_name FROM INFORMATION_SCHEMA.COLUMNS c +# WHERE c.table_name = '$pfs_view' ORDER BY c.ORDINAL_POSITION")) { +# push @$result{'sys'}{$pfs_view}{'headers'}, $h; +# } +# exit 1; +# $result{'sys'}{$pfs_view}{'values'}=(); +# for my $lQuery (select_array("select * from sys.$pfs_view")) { +# push $result{'sys'}{$pfs_view}{'values'}, $lQuery; +# } +# } + # Top user per connection + subheaderprint "Performance schema: Top 5 user per connection"; + my $nbL = 1; + for my $lQuery ( + select_array( +'select user, total_connections from sys.user_summary order by total_connections desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery conn(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per statement + subheaderprint "Performance schema: Top 5 user per statement"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, statements from sys.user_summary order by statements desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery stmt(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per statement latency + subheaderprint "Performance schema: Top 5 user per statement latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, statement_avg_latency from sys.x\\$user_summary order by statement_avg_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per lock latency + subheaderprint "Performance schema: Top 5 user per lock latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, lock_latency from sys.x\\$user_summary_by_statement_latency order by lock_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per full scans + subheaderprint "Performance schema: Top 5 user per nb full scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, full_scans from sys.x\\$user_summary_by_statement_latency order by full_scans desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per row_sent + subheaderprint "Performance schema: Top 5 user per rows sent"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, rows_sent from sys.x\\$user_summary_by_statement_latency order by rows_sent desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per row modified + subheaderprint "Performance schema: Top 5 user per rows modified"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, rows_affected from sys.x\\$user_summary_by_statement_latency order by rows_affected desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per io + subheaderprint "Performance schema: Top 5 user per IO"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, file_ios from sys.x\\$user_summary order by file_ios desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top user per io latency + subheaderprint "Performance schema: Top 5 user per IO latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, file_io_latency from sys.x\\$user_summary order by file_io_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per connection + subheaderprint "Performance schema: Top 5 host per connection"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, total_connections from sys.x\\$host_summary order by total_connections desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery conn(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per statement + subheaderprint "Performance schema: Top 5 host per statement"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, statements from sys.x\\$host_summary order by statements desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery stmt(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per statement latency + subheaderprint "Performance schema: Top 5 host per statement latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, statement_avg_latency from sys.x\\$host_summary order by statement_avg_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per lock latency + subheaderprint "Performance schema: Top 5 host per lock latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, lock_latency from sys.x\\$host_summary_by_statement_latency order by lock_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per full scans + subheaderprint "Performance schema: Top 5 host per nb full scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, full_scans from sys.x\\$host_summary_by_statement_latency order by full_scans desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per rows sent + subheaderprint "Performance schema: Top 5 host per rows sent"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, rows_sent from sys.x\\$host_summary_by_statement_latency order by rows_sent desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per rows modified + subheaderprint "Performance schema: Top 5 host per rows modified"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, rows_affected from sys.x\\$host_summary_by_statement_latency order by rows_affected desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per io + subheaderprint "Performance schema: Top 5 host per io"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, file_ios from sys.x\\$host_summary order by file_ios desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top 5 host per io latency + subheaderprint "Performance schema: Top 5 host per io latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, file_io_latency from sys.x\\$host_summary order by file_io_latency desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top IO type order by total io + subheaderprint "Performance schema: Top IO type order by total io"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select substring(event_name,14), SUM(total)AS total from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery i/o"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top IO type order by total latency + subheaderprint "Performance schema: Top IO type order by total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select substring(event_name,14), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total_latency DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top IO type order by max latency + subheaderprint "Performance schema: Top IO type order by max latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select substring(event_name,14), MAX(max_latency) as max_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY max_latency DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top Stages order by total io + subheaderprint "Performance schema: Top Stages order by total io"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select substring(event_name,7), SUM(total)AS total from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery i/o"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top Stages order by total latency + subheaderprint "Performance schema: Top Stages order by total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select substring(event_name,7), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total_latency DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top Stages order by avg latency + subheaderprint "Performance schema: Top Stages order by avg latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select substring(event_name,7), MAX(avg_latency) as avg_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY avg_latency DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top host per table scans + subheaderprint "Performance schema: Top 5 host per table scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select host, table_scans from sys.x\\$host_summary order by table_scans desc LIMIT 5' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # InnoDB Buffer Pool by schema + subheaderprint "Performance schema: InnoDB Buffer Pool by schema"; + $nbL = 1; + for my $lQuery ( + select_array( +'select object_schema, allocated, data, pages from sys.x\\$innodb_buffer_stats_by_schema ORDER BY pages DESC' + ) + ) + { + infoprint " +-- $nbL: $lQuery page(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # InnoDB Buffer Pool by table + subheaderprint "Performance schema: 40 InnoDB Buffer Pool by table"; + $nbL = 1; + for my $lQuery ( + select_array( +'select object_schema, object_name, allocated,data, pages from sys.x\\$innodb_buffer_stats_by_table ORDER BY pages DESC LIMIT 40' + ) + ) + { + infoprint " +-- $nbL: $lQuery page(s)"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Process per allocated memory + subheaderprint "Performance schema: Process per time"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, Command AS PROC, time from sys.x\\$processlist ORDER BY time DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # InnoDB Lock Waits + subheaderprint "Performance schema: InnoDB Lock Waits"; + $nbL = 1; + for my $lQuery ( + select_array( +'select wait_age_secs, locked_table, locked_type, waiting_query from sys.x\\$innodb_lock_waits order by wait_age_secs DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Threads IO Latency + subheaderprint "Performance schema: Thread IO Latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select user, total_latency, max_latency from sys.x\\$io_by_thread_by_latency order by total_latency DESC;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # High Cost SQL statements + subheaderprint "Performance schema: Top 15 Most latency statements"; + $nbL = 1; + for my $lQuery ( + select_array( +'select LEFT(query, 120), avg_latency from sys.x\\$statement_analysis order by avg_latency desc LIMIT 15' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top 5% slower queries + subheaderprint "Performance schema: Top 15 slower queries"; + $nbL = 1; + for my $lQuery ( + select_array( +'select LEFT(query, 120), exec_count from sys.x\\$statements_with_runtimes_in_95th_percentile order by exec_count desc LIMIT 15' + ) + ) + { + infoprint " +-- $nbL: $lQuery s"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top 10 nb statement type + subheaderprint "Performance schema: Top 15 nb statement type"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(total) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top statement by total latency + subheaderprint "Performance schema: Top 15 statement by total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(total_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top statement by lock latency + subheaderprint "Performance schema: Top 15 statement by lock latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(lock_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top statement by full scans + subheaderprint "Performance schema: Top 15 statement by full scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(full_scans) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top statement by rows sent + subheaderprint "Performance schema: Top 15 statement by rows sent"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(rows_sent) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Top statement by rows modified + subheaderprint "Performance schema: Top 15 statement by rows modified"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select statement, sum(rows_affected) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Use temporary tables + subheaderprint "Performance schema: 15 sample queries using temp table"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select left(query, 120) from sys.x\\$statements_with_temp_tables LIMIT 15' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Unused Indexes + subheaderprint "Performance schema: Unused indexes"; + $nbL = 1; + for my $lQuery ( + select_array( +"select \* from sys.schema_unused_indexes where object_schema not in ('performance_schema')" + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Full table scans + subheaderprint "Performance schema: Tables with full table scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select * from sys.x\\$schema_tables_with_full_table_scans order by rows_full_scanned DESC' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Latest file IO by latency + subheaderprint "Performance schema: Latest File IO by latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select thread, file, latency, operation from sys.x\\$latest_file_io ORDER BY latency LIMIT 10;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # FILE by IO read bytes + subheaderprint "Performance schema: File by IO read bytes"; + $nbL = 1; + for my $lQuery ( + select_array( +'select file, total_read from sys.x\\$io_global_by_file_by_bytes order by total_read DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # FILE by IO written bytes + subheaderprint "Performance schema: File by IO written bytes"; + $nbL = 1; + for my $lQuery ( + select_array( +'select file, total_written from sys.x\\$io_global_by_file_by_bytes order by total_written DESC LIMIT 15' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # file per IO total latency + subheaderprint "Performance schema: File per IO total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select file, total_latency from sys.x\\$io_global_by_file_by_latency ORDER BY total_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # file per IO read latency + subheaderprint "Performance schema: file per IO read latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select file, read_latency from sys.x\\$io_global_by_file_by_latency ORDER BY read_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # file per IO write latency + subheaderprint "Performance schema: file per IO write latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select file, write_latency from sys.x\\$io_global_by_file_by_latency ORDER BY write_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Event Wait by read bytes + subheaderprint "Performance schema: Event Wait by read bytes"; + $nbL = 1; + for my $lQuery ( + select_array( +'select event_name, total_read from sys.x\\$io_global_by_wait_by_bytes order by total_read DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Event Wait by write bytes + subheaderprint "Performance schema: Event Wait written bytes"; + $nbL = 1; + for my $lQuery ( + select_array( +'select event_name, total_written from sys.x\\$io_global_by_wait_by_bytes order by total_written DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # event per wait total latency + subheaderprint "Performance schema: event per wait total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select event_name, total_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY total_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # event per wait read latency + subheaderprint "Performance schema: event per wait read latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select event_name, read_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY read_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # event per wait write latency + subheaderprint "Performance schema: event per wait write latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select event_name, write_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY write_latency DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + #schema_index_statistics + # TOP 15 most read index + subheaderprint "Performance schema: TOP 15 most read indexes"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, rows_selected from sys.x\\$schema_index_statistics ORDER BY ROWs_selected DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 most used index + subheaderprint "Performance schema: Top 15 most modified indexes"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_index_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high read latency index + subheaderprint "Performance schema: Top 15 high read latency index"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, select_latency from sys.x\\$schema_index_statistics ORDER BY select_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high insert latency index + subheaderprint "Performance schema: Top 15 most modified indexes"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, insert_latency from sys.x\\$schema_index_statistics ORDER BY insert_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high update latency index + subheaderprint "Performance schema: Top 15 high update latency index"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, update_latency from sys.x\\$schema_index_statistics ORDER BY update_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high delete latency index + subheaderprint "Performance schema: Top 15 high delete latency index"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name,index_name, delete_latency from sys.x\\$schema_index_statistics ORDER BY delete_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 most read tables + subheaderprint "Performance schema: TOP 15 most read tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, rows_fetched from sys.x\\$schema_table_statistics ORDER BY ROWs_fetched DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 most used tables + subheaderprint "Performance schema: Top 15 most modified tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_table_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high read latency tables + subheaderprint "Performance schema: Top 15 high read latency tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, fetch_latency from sys.x\\$schema_table_statistics ORDER BY fetch_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high insert latency tables + subheaderprint "Performance schema: Top 15 high insert latency tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, insert_latency from sys.x\\$schema_table_statistics ORDER BY insert_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high update latency tables + subheaderprint "Performance schema: Top 15 high update latency tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, update_latency from sys.x\\$schema_table_statistics ORDER BY update_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # TOP 15 high delete latency tables + subheaderprint "Performance schema: Top 15 high delete latency tables"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select table_schema, table_name, delete_latency from sys.x\\$schema_table_statistics ORDER BY delete_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + # Redundant indexes + subheaderprint "Performance schema: Redundant indexes"; + $nbL = 1; + for my $lQuery ( + select_array('use sys;select * from schema_redundant_indexes;') ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Table not using InnoDB buffer"; + $nbL = 1; + for my $lQuery ( + select_array( +' Select table_schema, table_name from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NULL;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Top 15 Tables using InnoDB buffer"; + $nbL = 1; + for my $lQuery ( + select_array( +'select table_schema,table_name,innodb_buffer_allocated from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_allocated DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Top 15 Tables with InnoDB buffer free"; + $nbL = 1; + for my $lQuery ( + select_array( +'select table_schema,table_name,innodb_buffer_free from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_free DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Top 15 Most executed queries"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), exec_count from sys.x\\$statement_analysis order by exec_count DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: Latest SQL queries in errors or warnings"; + $nbL = 1; + for my $lQuery ( + select_array( +'select LEFT(query, 120), last_seen from sys.x\\$statements_with_errors_or_warnings ORDER BY last_seen LIMIT 40;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Top 20 queries with full table scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_full_table_scans order BY exec_count DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Last 50 queries with full table scans"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_full_table_scans order BY last_seen DESC LIMIT 50;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 reader queries (95% percentile)"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), rows_sent from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY ROWs_sent DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 most row look queries (95% percentile)"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), rows_examined AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY rows_examined DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 total latency queries (95% percentile)"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY total_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 max latency queries (95% percentile)"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), max_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY max_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 average latency queries (95% percentile)"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), avg_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY avg_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Top 20 queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_sorting order BY exec_count DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Last 50 queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_sorting order BY last_seen DESC LIMIT 50;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 row sorting queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), rows_sorted from sys.x\\$statements_with_sorting ORDER BY ROWs_sorted DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 total latency queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_sorting ORDER BY total_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 merge queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), sort_merge_passes AS search from sys.x\\$statements_with_sorting ORDER BY sort_merge_passes DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 average sort merges queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), avg_sort_merges AS search from sys.x\\$statements_with_sorting ORDER BY avg_sort_merges DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 scans queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), sorts_using_scans AS search from sys.x\\$statements_with_sorting ORDER BY sorts_using_scans DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 range queries with sort"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), sort_using_range AS search from sys.x\\$statements_with_sorting ORDER BY sort_using_range DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + +################################################################################## + + #statements_with_temp_tables + +#mysql> desc statements_with_temp_tables; +#+--------------------------+---------------------+------+-----+---------------------+-------+ +#| Field | Type | Null | Key | Default | Extra | +#+--------------------------+---------------------+------+-----+---------------------+-------+ +#| query | longtext | YES | | NULL | | +#| db | varchar(64) | YES | | NULL | | +#| exec_count | bigint(20) unsigned | NO | | NULL | | +#| total_latency | text | YES | | NULL | | +#| memory_tmp_tables | bigint(20) unsigned | NO | | NULL | | +#| disk_tmp_tables | bigint(20) unsigned | NO | | NULL | | +#| avg_tmp_tables_per_query | decimal(21,0) | NO | | 0 | | +#| tmp_tables_to_disk_pct | decimal(24,0) | NO | | 0 | | +#| first_seen | timestamp | NO | | 0000-00-00 00:00:00 | | +#| last_seen | timestamp | NO | | 0000-00-00 00:00:00 | | +#| digest | varchar(32) | YES | | NULL | | +#+--------------------------+---------------------+------+-----+---------------------+-------+ +#11 rows in set (0,01 sec)# +# + subheaderprint "Performance schema: Top 20 queries with temp table"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_temp_tables order BY exec_count DESC LIMIT 20;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: Last 50 queries with temp table"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_temp_tables order BY last_seen DESC LIMIT 50;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint + "Performance schema: TOP 15 total latency queries with temp table"; + $nbL = 1; + for my $lQuery ( + select_array( +'select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_temp_tables ORDER BY total_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 queries with temp table to disk"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select db, LEFT(query, 120), disk_tmp_tables from sys.x\\$statements_with_temp_tables ORDER BY disk_tmp_tables DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + +################################################################################## + #wait_classes_global_by_latency + +#ysql> select * from wait_classes_global_by_latency; +#-----------------+-------+---------------+-------------+-------------+-------------+ +# event_class | total | total_latency | min_latency | avg_latency | max_latency | +#-----------------+-------+---------------+-------------+-------------+-------------+ +# wait/io/file | 15381 | 1.23 s | 0 ps | 80.12 us | 230.64 ms | +# wait/io/table | 59 | 7.57 ms | 5.45 us | 128.24 us | 3.95 ms | +# wait/lock/table | 69 | 3.22 ms | 658.84 ns | 46.64 us | 1.10 ms | +#-----------------+-------+---------------+-------------+-------------+-------------+ +# rows in set (0,00 sec) + + subheaderprint "Performance schema: TOP 15 class events by number"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select event_class, total from sys.x\\$wait_classes_global_by_latency ORDER BY total DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 30 events by number"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select events, total from sys.x\\$waits_global_by_latency ORDER BY total DESC LIMIT 30;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 class events by total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select event_class, total_latency from sys.x\\$wait_classes_global_by_latency ORDER BY total_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 30 events by total latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'use sys;select events, total_latency from sys.x\\$waits_global_by_latency ORDER BY total_latency DESC LIMIT 30;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 15 class events by max latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select event_class, max_latency from sys.x\\$wait_classes_global_by_latency ORDER BY max_latency DESC LIMIT 15;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + + subheaderprint "Performance schema: TOP 30 events by max latency"; + $nbL = 1; + for my $lQuery ( + select_array( +'select events, max_latency from sys.x\\$waits_global_by_latency ORDER BY max_latency DESC LIMIT 30;' + ) + ) + { + infoprint " +-- $nbL: $lQuery"; + $nbL++; + } + infoprint "No information found or indicators deactivated." + if ( $nbL == 1 ); + +} + +# Recommendations for Aria Engine +sub mariadb_aria { + subheaderprint "Aria Metrics"; + + # Aria + if ( !defined $myvar{'have_aria'} ) { + infoprint "Aria Storage Engine not available."; + return; + } + if ( $myvar{'have_aria'} ne "YES" ) { + infoprint "Aria Storage Engine is disabled."; + return; + } + infoprint "Aria Storage Engine is enabled."; + + # Aria pagecache + if ( !defined( $mycalc{'total_aria_indexes'} ) ) { + push( @generalrec, + "Unable to calculate Aria index size on MySQL server" ); + } + else { + if ( + $myvar{'aria_pagecache_buffer_size'} < $mycalc{'total_aria_indexes'} + && $mycalc{'pct_aria_keys_from_mem'} < 95 ) + { + badprint "Aria pagecache size / total Aria indexes: " + . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_aria_indexes'} ) . ""; + push( @adjvars, + "aria_pagecache_buffer_size (> " + . hr_bytes( $mycalc{'total_aria_indexes'} ) + . ")" ); + } + else { + goodprint "Aria pagecache size / total Aria indexes: " + . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_aria_indexes'} ) . ""; + } + if ( $mystat{'Aria_pagecache_read_requests'} > 0 ) { + if ( $mycalc{'pct_aria_keys_from_mem'} < 95 ) { + badprint +"Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% (" + . hr_num( $mystat{'Aria_pagecache_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Aria_pagecache_reads'} ) + . " reads)"; + } + else { + goodprint +"Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% (" + . hr_num( $mystat{'Aria_pagecache_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Aria_pagecache_reads'} ) + . " reads)"; + } + } + else { + + # No queries have run that would use keys + } + } +} + +# Recommendations for TokuDB +sub mariadb_tokudb { + subheaderprint "TokuDB Metrics"; + + # AriaDB + unless ( defined $myvar{'have_tokudb'} + && $myvar{'have_tokudb'} eq "YES" ) + { + infoprint "TokuDB is disabled."; + return; + } + infoprint "TokuDB is enabled."; + + # All is to done here +} + +# Recommendations for XtraDB +sub mariadb_xtradb { + subheaderprint "XtraDB Metrics"; + + # XtraDB + unless ( defined $myvar{'have_xtradb'} + && $myvar{'have_xtradb'} eq "YES" ) + { + infoprint "XtraDB is disabled."; + return; + } + infoprint "XtraDB is enabled."; + infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." + + # All is to done here +} + +# Recommendations for RocksDB +sub mariadb_rockdb { + subheaderprint "RocksDB Metrics"; + + # RocksDB + unless ( defined $myvar{'have_rocksdb'} + && $myvar{'have_rocksdb'} eq "YES" ) + { + infoprint "RocksDB is disabled."; + return; + } + infoprint "RocksDB is enabled."; + + # All is to do here +} + +# Recommendations for Spider +sub mariadb_spider { + subheaderprint "Spider Metrics"; + + # Spider + unless ( defined $myvar{'have_spider'} + && $myvar{'have_spider'} eq "YES" ) + { + infoprint "Spider is disabled."; + return; + } + infoprint "Spider is enabled."; + + # All is to do here +} + +# Recommendations for Connect +sub mariadb_connect { + subheaderprint "Connect Metrics"; + + # Connect + unless ( defined $myvar{'have_connect'} + && $myvar{'have_connect'} eq "YES" ) + { + infoprint "Connect is disabled."; + return; + } + infoprint "Connect is enabled."; + + # All is to do here +} + +# Perl trim function to remove whitespace from the start and end of the string +sub trim { + my $string = shift; + return "" unless defined($string); + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} + +sub get_wsrep_options { + return () unless defined $myvar{'wsrep_provider_options'}; + + my @galera_options = split /;/, $myvar{'wsrep_provider_options'}; + my $wsrep_slave_threads = $myvar{'wsrep_slave_threads'}; + push @galera_options, ' wsrep_slave_threads = ' . $wsrep_slave_threads; + @galera_options = remove_cr @galera_options; + @galera_options = remove_empty @galera_options; + + #debugprint Dumper( \@galera_options ); + return @galera_options; +} + +sub get_gcache_memory { + my $gCacheMem = hr_raw( get_wsrep_option('gcache.size') ); + + return 0 unless defined $gCacheMem and $gCacheMem ne ''; + return $gCacheMem; +} + +sub get_wsrep_option { + my $key = shift; + return '' unless defined $myvar{'wsrep_provider_options'}; + my @galera_options = get_wsrep_options; + return '' unless scalar(@galera_options) > 0; + my @memValues = grep /\s*$key =/, @galera_options; + my $memValue = $memValues[0]; + return 0 unless defined $memValue; + $memValue =~ s/.*=\s*(.+)$/$1/g; + return $memValue; +} + +# Recommendations for Galera +sub mariadb_galera { + subheaderprint "Galera Metrics"; + + # Galera Cluster + unless ( defined $myvar{'have_galera'} + && $myvar{'have_galera'} eq "YES" ) + { + infoprint "Galera is disabled."; + return; + } + infoprint "Galera is enabled."; + debugprint "Galera variables:"; + foreach my $gvar ( keys %myvar ) { + next unless $gvar =~ /^wsrep.*/; + next if $gvar eq 'wsrep_provider_options'; + debugprint "\t" . trim($gvar) . " = " . $myvar{$gvar}; + $result{'Galera'}{'variables'}{$gvar} = $myvar{$gvar}; + } + if ( not defined( $myvar{'wsrep_on'} ) or $myvar{'wsrep_on'} ne "ON" ) { + infoprint "Galera is disabled."; + return; + } + debugprint "Galera wsrep provider Options:"; + my @galera_options = get_wsrep_options; + $result{'Galera'}{'wsrep options'} = get_wsrep_options(); + foreach my $gparam (@galera_options) { + debugprint "\t" . trim($gparam); + } + debugprint "Galera status:"; + foreach my $gstatus ( keys %mystat ) { + next unless $gstatus =~ /^wsrep.*/; + debugprint "\t" . trim($gstatus) . " = " . $mystat{$gstatus}; + $result{'Galera'}{'status'}{$gstatus} = $myvar{$gstatus}; + } + infoprint "GCache is using " + . hr_bytes_rnd( get_wsrep_option('gcache.mem_size') ); + + #my @primaryKeysNbTables=(); + my @primaryKeysNbTables = select_array( + "Select CONCAT(c.table_schema,CONCAT('.', c.table_name)) +from information_schema.columns c +join information_schema.tables t using (TABLE_SCHEMA, TABLE_NAME) +where c.table_schema not in ('mysql', 'information_schema', 'performance_schema') + and t.table_type != 'VIEW' +group by c.table_schema,c.table_name +having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" + ); + + infoprint "CPU core detected : " . (cpu_cores); + infoprint "wsrep_slave_threads: " . get_wsrep_option('wsrep_slave_threads'); + + if ( get_wsrep_option('wsrep_slave_threads') > ( (cpu_cores) * 4 ) + or get_wsrep_option('wsrep_slave_threads') < ( (cpu_cores) * 2 ) ) + { + badprint +"wsrep_slave_threads is not equal to 2, 3 or 4 times number of CPU(s)"; + push @adjvars, "wsrep_slave_threads = " . ( (cpu_cores) * 4 ); + } + else { + goodprint + "wsrep_slave_threads is equal to 2, 3 or 4 times number of CPU(s)"; + } + + if ( get_wsrep_option('wsrep_slave_threads') > 1 ) { + infoprint + "wsrep parallel slave can cause frequent inconsistency crash."; + push @adjvars, +"Set wsrep_slave_threads to 1 in case of HA_ERR_FOUND_DUPP_KEY crash on slave"; + + # check options for parallel slave + if ( get_wsrep_option('wsrep_slave_FK_checks') eq "OFF" ) { + badprint "wsrep_slave_FK_checks is off with parallel slave"; + push @adjvars, + "wsrep_slave_FK_checks should be ON when using parallel slave"; + } + + # wsrep_slave_UK_checks seems useless in MySQL source code + if ( $myvar{'innodb_autoinc_lock_mode'} != 2 ) { + badprint + "innodb_autoinc_lock_mode is incorrect with parallel slave"; + push @adjvars, + "innodb_autoinc_lock_mode should be 2 when using parallel slave"; + } + } + + if ( get_wsrep_option('gcs.fc_limit') != $myvar{'wsrep_slave_threads'} * 5 ) + { + badprint "gcs.fc_limit should be equal to 5 * wsrep_slave_threads (=" + . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; + push @adjvars, "gcs.fc_limit= wsrep_slave_threads * 5 (=" + . ( $myvar{'wsrep_slave_threads'} * 5 ) . ")"; + } + else { + goodprint "gcs.fc_limit is equal to 5 * wsrep_slave_threads ( =" + . get_wsrep_option('gcs.fc_limit') . ")"; + } + + if ( get_wsrep_option('gcs.fc_factor') != 0.8 ) { + badprint "gcs.fc_factor should be equal to 0.8 (=" + . get_wsrep_option('gcs.fc_factor') . ")"; + push @adjvars, "gcs.fc_factor=0.8"; + } + else { + goodprint "gcs.fc_factor is equal to 0.8"; + } + if ( get_wsrep_option('wsrep_flow_control_paused') > 0.02 ) { + badprint "Fraction of time node pause flow control > 0.02"; + } + else { + goodprint +"Flow control fraction seems to be OK (wsrep_flow_control_paused<=0.02)"; + } + + if ( scalar(@primaryKeysNbTables) > 0 ) { + badprint "Following table(s) don't have primary key:"; + foreach my $badtable (@primaryKeysNbTables) { + badprint "\t$badtable"; + push @{ $result{'Tables without PK'} }, $badtable; + } + } + else { + goodprint "All tables get a primary key"; + } + my @nonInnoDBTables = select_array( +"select CONCAT(table_schema,CONCAT('.', table_name)) from information_schema.tables where ENGINE <> 'InnoDB' and table_schema not in ('mysql', 'performance_schema', 'information_schema')" + ); + if ( scalar(@nonInnoDBTables) > 0 ) { + badprint "Following table(s) are not InnoDB table:"; + push @generalrec, + "Ensure that all table(s) are InnoDB tables for Galera replication"; + foreach my $badtable (@nonInnoDBTables) { + badprint "\t$badtable"; + } + } + else { + goodprint "All tables are InnoDB tables"; + } + if ( $myvar{'binlog_format'} ne 'ROW' ) { + badprint "Binlog format should be in ROW mode."; + push @adjvars, "binlog_format = ROW"; + } + else { + goodprint "Binlog format is in ROW mode."; + } + if ( $myvar{'innodb_flush_log_at_trx_commit'} != 0 ) { + badprint "InnoDB flush log at each commit should be disabled."; + push @adjvars, "innodb_flush_log_at_trx_commit = 0"; + } + else { + goodprint "InnoDB flush log at each commit is disabled for Galera."; + } + + infoprint "Read consistency mode :" . $myvar{'wsrep_causal_reads'}; + + if ( defined( $myvar{'wsrep_cluster_name'} ) + and $myvar{'wsrep_on'} eq "ON" ) + { + goodprint "Galera WsREP is enabled."; + if ( defined( $myvar{'wsrep_cluster_address'} ) + and trim("$myvar{'wsrep_cluster_address'}") ne "" ) + { + goodprint "Galera Cluster address is defined: " + . $myvar{'wsrep_cluster_address'}; + my @NodesTmp = split /,/, $myvar{'wsrep_cluster_address'}; + my $nbNodes = @NodesTmp; + infoprint "There are $nbNodes nodes in wsrep_cluster_address"; + my $nbNodesSize = trim( $mystat{'wsrep_cluster_size'} ); + if ( $nbNodesSize == 3 or $nbNodesSize == 5 ) { + goodprint "There are $nbNodesSize nodes in wsrep_cluster_size."; + } + else { + badprint +"There are $nbNodesSize nodes in wsrep_cluster_size. Prefer 3 or 5 nodes architecture."; + push @generalrec, "Prefer 3 or 5 nodes architecture."; + } + + # wsrep_cluster_address doesn't include garbd nodes + if ( $nbNodes > $nbNodesSize ) { + badprint +"All cluster nodes are not detected. wsrep_cluster_size less then node count in wsrep_cluster_address"; + } + else { + goodprint "All cluster nodes detected."; + } + } + else { + badprint "Galera Cluster address is undefined"; + push @adjvars, + "set up wsrep_cluster_address variable for Galera replication"; + } + if ( defined( $myvar{'wsrep_cluster_name'} ) + and trim( $myvar{'wsrep_cluster_name'} ) ne "" ) + { + goodprint "Galera Cluster name is defined: " + . $myvar{'wsrep_cluster_name'}; + } + else { + badprint "Galera Cluster name is undefined"; + push @adjvars, + "set up wsrep_cluster_name variable for Galera replication"; + } + if ( defined( $myvar{'wsrep_node_name'} ) + and trim( $myvar{'wsrep_node_name'} ) ne "" ) + { + goodprint "Galera Node name is defined: " + . $myvar{'wsrep_node_name'}; + } + else { + badprint "Galera node name is undefined"; + push @adjvars, + "set up wsrep_node_name variable for Galera replication"; + } + if ( trim( $myvar{'wsrep_notify_cmd'} ) ne "" ) { + goodprint "Galera Notify command is defined."; + } + else { + badprint "Galera Notify command is not defined."; + push( @adjvars, "set up parameter wsrep_notify_cmd to be notify" ); + } + if ( trim( $myvar{'wsrep_sst_method'} ) !~ "^xtrabackup.*" + and trim( $myvar{'wsrep_sst_method'} ) !~ "^mariabackup" ) + { + badprint "Galera SST method is not xtrabackup based."; + push( @adjvars, +"set up parameter wsrep_sst_method to xtrabackup based parameter" + ); + } + else { + goodprint "SST Method is based on xtrabackup."; + } + if ( + ( + defined( $myvar{'wsrep_OSU_method'} ) + && trim( $myvar{'wsrep_OSU_method'} ) eq "TOI" + ) + || ( defined( $myvar{'wsrep_osu_method'} ) + && trim( $myvar{'wsrep_osu_method'} ) eq "TOI" ) + ) + { + goodprint "TOI is default mode for upgrade."; + } + else { + badprint "Schema upgrade are not replicated automatically"; + push( @adjvars, "set up parameter wsrep_OSU_method to TOI" ); + } + infoprint "Max WsRep message : " + . hr_bytes( $myvar{'wsrep_max_ws_size'} ); + } + else { + badprint "Galera WsREP is disabled"; + } + + if ( defined( $mystat{'wsrep_connected'} ) + and $mystat{'wsrep_connected'} eq "ON" ) + { + goodprint "Node is connected"; + } + else { + badprint "Node is disconnected"; + } + if ( defined( $mystat{'wsrep_ready'} ) and $mystat{'wsrep_ready'} eq "ON" ) + { + goodprint "Node is ready"; + } + else { + badprint "Node is not ready"; + } + infoprint "Cluster status :" . $mystat{'wsrep_cluster_status'}; + if ( defined( $mystat{'wsrep_cluster_status'} ) + and $mystat{'wsrep_cluster_status'} eq "Primary" ) + { + goodprint "Galera cluster is consistent and ready for operations"; + } + else { + badprint "Cluster is not consistent and ready"; + } + if ( $mystat{'wsrep_local_state_uuid'} eq + $mystat{'wsrep_cluster_state_uuid'} ) + { + goodprint "Node and whole cluster at the same level: " + . $mystat{'wsrep_cluster_state_uuid'}; + } + else { + badprint "Node and whole cluster not the same level"; + infoprint "Node state uuid: " . $mystat{'wsrep_local_state_uuid'}; + infoprint "Cluster state uuid: " . $mystat{'wsrep_cluster_state_uuid'}; + } + if ( $mystat{'wsrep_local_state_comment'} eq 'Synced' ) { + goodprint "Node is synced with whole cluster."; + } + else { + badprint "Node is not synced"; + infoprint "Node State : " . $mystat{'wsrep_local_state_comment'}; + } + if ( $mystat{'wsrep_local_cert_failures'} == 0 ) { + goodprint "There is no certification failures detected."; + } + else { + badprint "There is " + . $mystat{'wsrep_local_cert_failures'} + . " certification failure(s)detected."; + } + + for my $key ( keys %mystat ) { + if ( $key =~ /wsrep_|galera/i ) { + debugprint "WSREP: $key = $mystat{$key}"; + } + } + + #debugprint Dumper get_wsrep_options(); +} + +# Recommendations for InnoDB +sub mysql_innodb { + subheaderprint "InnoDB Metrics"; + + # InnoDB + unless ( defined $myvar{'have_innodb'} + && $myvar{'have_innodb'} eq "YES" ) + { + infoprint "InnoDB is disabled."; + if ( mysql_version_ge( 5, 5 ) ) { + my $defengine = 'InnoDB'; + $defengine = $myvar{'default_storage_engine'} + if defined( $myvar{'default_storage_engine'} ); + badprint +"InnoDB Storage engine is disabled. $defengine is the default storage engine" + if $defengine eq 'InnoDB'; + infoprint +"InnoDB Storage engine is disabled. $defengine is the default storage engine" + if $defengine ne 'InnoDB'; + } + return; + } + infoprint "InnoDB is enabled."; + if ( !defined $enginestats{'InnoDB'} ) { + if ( $opt{skipsize} eq 1 ) { + infoprint "Skipped due to --skipsize option"; + return; + } + badprint "No tables are Innodb"; + $enginestats{'InnoDB'} = 0; + } + + if ( $opt{buffers} ne 0 ) { + infoprint "InnoDB Buffers"; + if ( defined $myvar{'innodb_buffer_pool_size'} ) { + infoprint " +-- InnoDB Buffer Pool: " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . ""; + } + if ( defined $myvar{'innodb_buffer_pool_instances'} ) { + infoprint " +-- InnoDB Buffer Pool Instances: " + . $myvar{'innodb_buffer_pool_instances'} . ""; + } + + if ( defined $myvar{'innodb_buffer_pool_chunk_size'} ) { + infoprint " +-- InnoDB Buffer Pool Chunk Size: " + . hr_bytes( $myvar{'innodb_buffer_pool_chunk_size'} ) . ""; + } + if ( defined $myvar{'innodb_additional_mem_pool_size'} ) { + infoprint " +-- InnoDB Additional Mem Pool: " + . hr_bytes( $myvar{'innodb_additional_mem_pool_size'} ) . ""; + } + if ( defined $myvar{'innodb_log_file_size'} ) { + infoprint " +-- InnoDB Log File Size: " + . hr_bytes( $myvar{'innodb_log_file_size'} ); + } + if ( defined $myvar{'innodb_log_files_in_group'} ) { + infoprint " +-- InnoDB Log File In Group: " + . $myvar{'innodb_log_files_in_group'}; + } + if ( defined $myvar{'innodb_log_files_in_group'} ) { + infoprint " +-- InnoDB Total Log File Size: " + . hr_bytes( $myvar{'innodb_log_files_in_group'} * + $myvar{'innodb_log_file_size'} ) + . "(" + . $mycalc{'innodb_log_size_pct'} + . " % of buffer pool)"; + } + if ( defined $myvar{'innodb_log_buffer_size'} ) { + infoprint " +-- InnoDB Log Buffer: " + . hr_bytes( $myvar{'innodb_log_buffer_size'} ); + } + if ( defined $mystat{'Innodb_buffer_pool_pages_free'} ) { + infoprint " +-- InnoDB Log Buffer Free: " + . hr_bytes( $mystat{'Innodb_buffer_pool_pages_free'} ) . ""; + } + if ( defined $mystat{'Innodb_buffer_pool_pages_total'} ) { + infoprint " +-- InnoDB Log Buffer Used: " + . hr_bytes( $mystat{'Innodb_buffer_pool_pages_total'} ) . ""; + } + } + if ( defined $myvar{'innodb_thread_concurrency'} ) { + infoprint "InnoDB Thread Concurrency: " + . $myvar{'innodb_thread_concurrency'}; + } + + # InnoDB Buffer Pool Size + if ( $myvar{'innodb_file_per_table'} eq "ON" ) { + goodprint "InnoDB File per table is activated"; + } + else { + badprint "InnoDB File per table is not activated"; + push( @adjvars, "innodb_file_per_table=ON" ); + } + + # InnoDB Buffer Pool Size + if ( $myvar{'innodb_buffer_pool_size'} > $enginestats{'InnoDB'} ) { + goodprint "InnoDB buffer pool / data size: " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . "/" + . hr_bytes( $enginestats{'InnoDB'} ) . ""; + } + else { + badprint "InnoDB buffer pool / data size: " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . "/" + . hr_bytes( $enginestats{'InnoDB'} ) . ""; + push( @adjvars, + "innodb_buffer_pool_size (>= " + . hr_bytes( $enginestats{'InnoDB'} ) + . ") if possible." ); + } + if ( $mycalc{'innodb_log_size_pct'} < 20 + or $mycalc{'innodb_log_size_pct'} > 30 ) + { + badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" + . $mycalc{'innodb_log_size_pct'} . "%): " + . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " + . $myvar{'innodb_log_files_in_group'} . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + push( + @adjvars, + "innodb_log_file_size should be (=" + . hr_bytes_rnd( + $myvar{'innodb_buffer_pool_size'} / + $myvar{'innodb_log_files_in_group'} / 4 + ) + . ") if possible, so InnoDB total log files size equals 25% of buffer pool size." + ); + if ( mysql_version_le( 5, 6, 2 ) ) { + push( @generalrec, +"For MySQL 5.6.2 and lower, Max combined innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." + ); + } + push( @generalrec, +"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" + ); + } + else { + goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " + . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " + . $myvar{'innodb_log_files_in_group'} . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + } + + # InnoDB Buffer Pool Instances (MySQL 5.6.6+) + if ( not mysql_version_ge( 10, 4 ) + and defined( $myvar{'innodb_buffer_pool_instances'} ) ) + { + + # Bad Value if > 64 + if ( $myvar{'innodb_buffer_pool_instances'} > 64 ) { + badprint "InnoDB buffer pool instances: " + . $myvar{'innodb_buffer_pool_instances'} . ""; + push( @adjvars, "innodb_buffer_pool_instances (<= 64)" ); + } + + # InnoDB Buffer Pool Size > 1Go + if ( $myvar{'innodb_buffer_pool_size'} > 1024 * 1024 * 1024 ) { + +# InnoDB Buffer Pool Size / 1Go = InnoDB Buffer Pool Instances limited to 64 max. + + # InnoDB Buffer Pool Size > 64Go + my $max_innodb_buffer_pool_instances = + int( $myvar{'innodb_buffer_pool_size'} / ( 1024 * 1024 * 1024 ) ); + $max_innodb_buffer_pool_instances = 64 + if ( $max_innodb_buffer_pool_instances > 64 ); + + if ( $myvar{'innodb_buffer_pool_instances'} != + $max_innodb_buffer_pool_instances ) + { + badprint "InnoDB buffer pool instances: " + . $myvar{'innodb_buffer_pool_instances'} . ""; + push( @adjvars, + "innodb_buffer_pool_instances(=" + . $max_innodb_buffer_pool_instances + . ")" ); + } + else { + goodprint "InnoDB buffer pool instances: " + . $myvar{'innodb_buffer_pool_instances'} . ""; + } + + # InnoDB Buffer Pool Size < 1Go + } + else { + if ( $myvar{'innodb_buffer_pool_instances'} != 1 ) { + badprint +"InnoDB buffer pool <= 1G and Innodb_buffer_pool_instances(!=1)."; + push( @adjvars, "innodb_buffer_pool_instances (=1)" ); + } + else { + goodprint "InnoDB buffer pool instances: " + . $myvar{'innodb_buffer_pool_instances'} . ""; + } + } + } + + # InnoDB Used Buffer Pool Size vs CHUNK size + if ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) ) { + infoprint + "InnoDB Buffer Pool Chunk Size not used or defined in your version"; + } + else { + infoprint "Number of InnoDB Buffer Pool Chunk: " + . int( $myvar{'innodb_buffer_pool_size'} ) / + int( $myvar{'innodb_buffer_pool_chunk_size'} ) . " for " + . $myvar{'innodb_buffer_pool_instances'} + . " Buffer Pool Instance(s)"; + + if ( + int( $myvar{'innodb_buffer_pool_size'} ) % ( + int( $myvar{'innodb_buffer_pool_chunk_size'} ) * + int( $myvar{'innodb_buffer_pool_instances'} ) + ) eq 0 + ) + { + goodprint +"Innodb_buffer_pool_size aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances"; + } + else { + badprint +"Innodb_buffer_pool_size aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances"; + +#push( @adjvars, "Adjust innodb_buffer_pool_instances, innodb_buffer_pool_chunk_size with innodb_buffer_pool_size" ); + push( @adjvars, +"innodb_buffer_pool_size must always be equal to or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances" + ); + } + } + + # InnoDB Read efficiency + if ( defined $mycalc{'pct_read_efficiency'} + && $mycalc{'pct_read_efficiency'} < 90 ) + { + badprint "InnoDB Read buffer efficiency: " + . $mycalc{'pct_read_efficiency'} . "% (" + . ( $mystat{'Innodb_buffer_pool_read_requests'} - + $mystat{'Innodb_buffer_pool_reads'} ) + . " hits/ " + . $mystat{'Innodb_buffer_pool_read_requests'} + . " total)"; + } + else { + goodprint "InnoDB Read buffer efficiency: " + . $mycalc{'pct_read_efficiency'} . "% (" + . ( $mystat{'Innodb_buffer_pool_read_requests'} - + $mystat{'Innodb_buffer_pool_reads'} ) + . " hits/ " + . $mystat{'Innodb_buffer_pool_read_requests'} + . " total)"; + } + + # InnoDB Write efficiency + if ( defined $mycalc{'pct_write_efficiency'} + && $mycalc{'pct_write_efficiency'} < 90 ) + { + badprint "InnoDB Write Log efficiency: " + . abs( $mycalc{'pct_write_efficiency'} ) . "% (" + . abs( $mystat{'Innodb_log_write_requests'} - + $mystat{'Innodb_log_writes'} ) + . " hits/ " + . $mystat{'Innodb_log_write_requests'} + . " total)"; + } + else { + goodprint "InnoDB Write log efficiency: " + . $mycalc{'pct_write_efficiency'} . "% (" + . ( $mystat{'Innodb_log_write_requests'} - + $mystat{'Innodb_log_writes'} ) + . " hits/ " + . $mystat{'Innodb_log_write_requests'} + . " total)"; + } + + # InnoDB Log Waits + $mystat{'Innodb_log_waits_computed'} = 0; + + if ( defined( $mystat{'Innodb_log_waits'} ) + and defined( $mystat{'Innodb_log_writes'} ) ) + { + $mystat{'Innodb_log_waits_computed'} = + $mystat{'Innodb_log_waits'} / $mystat{'Innodb_log_writes'}; + } + else { + undef $mystat{'Innodb_log_waits_computed'}; + } + + if ( defined $mystat{'Innodb_log_waits_computed'} + && $mystat{'Innodb_log_waits_computed'} > 0.000001 ) + { + badprint "InnoDB log waits: " + . percentage( $mystat{'Innodb_log_waits'}, + $mystat{'Innodb_log_writes'} ) + . "% (" + . $mystat{'Innodb_log_waits'} + . " waits / " + . $mystat{'Innodb_log_writes'} + . " writes)"; + push( @adjvars, + "innodb_log_buffer_size (> " + . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} ) + . ")" ); + } + else { + goodprint "InnoDB log waits: " + . percentage( $mystat{'Innodb_log_waits'}, + $mystat{'Innodb_log_writes'} ) + . "% (" + . $mystat{'Innodb_log_waits'} + . " waits / " + . $mystat{'Innodb_log_writes'} + . " writes)"; + } + $result{'Calculations'} = {%mycalc}; +} + +sub check_metadata_perf { + subheaderprint "Analysis Performance Metrics"; + if ( defined $myvar{'innodb_stats_on_metadata'} ) { + infoprint "innodb_stats_on_metadata: " + . $myvar{'innodb_stats_on_metadata'}; + if ( $myvar{'innodb_stats_on_metadata'} eq 'ON' ) { + badprint "Stat are updated during querying INFORMATION_SCHEMA."; + push @adjvars, "SET innodb_stats_on_metadata = OFF"; + + #Disabling innodb_stats_on_metadata + select_one("SET GLOBAL innodb_stats_on_metadata = OFF;"); + return 1; + } + } + goodprint "No stat updates during querying INFORMATION_SCHEMA."; + return 0; +} + +# Recommendations for Database metrics +sub mysql_databases { + return if ( $opt{dbstat} == 0 ); + + subheaderprint "Database Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint +"Skip Database metrics from information schema missing in this version"; + return; + } + + @dblist = select_array( +"SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" + ); + infoprint "There is " . scalar(@dblist) . " Database(s)."; + my @totaldbinfo = split /\s/, + select_one( +"SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" + ); + infoprint "All User Databases:"; + infoprint " +-- TABLE : " + . select_one( +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" + ) . ""; + infoprint " +-- VIEW : " + . select_one( +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" + ) . ""; + infoprint " +-- INDEX : " + . select_one( +"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" + ) . ""; + + infoprint " +-- CHARS : " + . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" + . ( + join ", ", + select_array( +"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" + ) + ) . ")"; + infoprint " +-- COLLA : " + . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" + . ( + join ", ", + select_array( +"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_COLLATION IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" + ) + ) . ")"; + infoprint " +-- ROWS : " + . ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ) . ""; + infoprint " +-- DATA : " + . hr_bytes( $totaldbinfo[1] ) . "(" + . percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%)"; + infoprint " +-- INDEX : " + . hr_bytes( $totaldbinfo[2] ) . "(" + . percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%)"; + infoprint " +-- SIZE : " . hr_bytes( $totaldbinfo[3] ) . ""; + infoprint " +-- ENGINE: " + . ( $totaldbinfo[6] eq 'NULL' ? 0 : $totaldbinfo[6] ) . " (" + . ( + join ", ", + select_array( +"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE ENGINE IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" + ) + ) . ")"; + + $result{'Databases'}{'All databases'}{'Rows'} = + ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ); + $result{'Databases'}{'All databases'}{'Data Size'} = $totaldbinfo[1]; + $result{'Databases'}{'All databases'}{'Data Pct'} = + percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%"; + $result{'Databases'}{'All databases'}{'Index Size'} = $totaldbinfo[2]; + $result{'Databases'}{'All databases'}{'Index Pct'} = + percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%"; + $result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3]; + print "\n" unless ( $opt{'silent'} or $opt{'json'} ); + + foreach (@dblist) { + my @dbinfo = split /\s/, + select_one( +"SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE),COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA" + ); + next unless defined $dbinfo[0]; + infoprint "Database: " . $dbinfo[0] . ""; + infoprint " +-- TABLE : " + . select_one( +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$_'" + ) . ""; + infoprint " +-- VIEW : " + . select_one( +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA='$_'" + ) . ""; + infoprint " +-- INDEX : " + . select_one( +"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA='$_'" + ) . ""; + infoprint " +-- CHARS : " + . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " (" + . ( + join ", ", + select_array( +"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA='$_';" + ) + ) . ")"; + infoprint " +-- COLLA : " + . ( $dbinfo[7] eq 'NULL' ? 0 : $dbinfo[7] ) . " (" + . ( + join ", ", + select_array( +"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND TABLE_COLLATION IS NOT NULL;" + ) + ) . ")"; + infoprint " +-- ROWS : " + . ( !defined( $dbinfo[1] ) or $dbinfo[1] eq 'NULL' ? 0 : $dbinfo[1] ) + . ""; + infoprint " +-- DATA : " + . hr_bytes( $dbinfo[2] ) . "(" + . percentage( $dbinfo[2], $dbinfo[4] ) . "%)"; + infoprint " +-- INDEX : " + . hr_bytes( $dbinfo[3] ) . "(" + . percentage( $dbinfo[3], $dbinfo[4] ) . "%)"; + infoprint " +-- TOTAL : " . hr_bytes( $dbinfo[4] ) . ""; + infoprint " +-- ENGINE: " + . ( $dbinfo[8] eq 'NULL' ? 0 : $dbinfo[8] ) . " (" + . ( + join ", ", + select_array( +"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL" + ) + ) . ")"; + + foreach my $eng ( + select_array( +"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL" + ) + ) + { + infoprint " +-- ENGINE $eng : " + . select_one( +"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbinfo[0]' AND ENGINE='$eng'" + ) . " TABLE(s)"; + } + badprint "Index size is larger than data size for $dbinfo[0] \n" + if ( $dbinfo[2] ne 'NULL' ) + and ( $dbinfo[3] ne 'NULL' ) + and ( $dbinfo[2] < $dbinfo[3] ); + unless ( $dbinfo[5] == 1 ) { + badprint "There are " + . $dbinfo[5] + . " storage engines. Be careful. \n"; + push @generalrec, +"Select one storage engine (InnoDB is a good choice) for all tables in $dbinfo[0] database ($dbinfo[5] engines detected)"; + } + $result{'Databases'}{ $dbinfo[0] }{'Rows'} = $dbinfo[1]; + $result{'Databases'}{ $dbinfo[0] }{'Tables'} = $dbinfo[6]; + $result{'Databases'}{ $dbinfo[0] }{'Collations'} = $dbinfo[7]; + $result{'Databases'}{ $dbinfo[0] }{'Data Size'} = $dbinfo[2]; + $result{'Databases'}{ $dbinfo[0] }{'Data Pct'} = + percentage( $dbinfo[2], $dbinfo[4] ) . "%"; + $result{'Databases'}{ $dbinfo[0] }{'Index Size'} = $dbinfo[3]; + $result{'Databases'}{ $dbinfo[0] }{'Index Pct'} = + percentage( $dbinfo[3], $dbinfo[4] ) . "%"; + $result{'Databases'}{ $dbinfo[0] }{'Total Size'} = $dbinfo[4]; + + if ( $dbinfo[7] > 1 ) { + badprint $dbinfo[7] + . " different collations for database " + . $dbinfo[0]; + push( @generalrec, + "Check all table collations are identical for all tables in " + . $dbinfo[0] + . " database." ); + } + else { + goodprint $dbinfo[7] + . " collation for " + . $dbinfo[0] + . " database."; + } + if ( $dbinfo[8] > 1 ) { + badprint $dbinfo[8] + . " different engines for database " + . $dbinfo[0]; + push( @generalrec, + "Check all table engines are identical for all tables in " + . $dbinfo[0] + . " database." ); + } + else { + goodprint $dbinfo[8] . " engine for " . $dbinfo[0] . " database."; + } + + my @distinct_column_charset = select_array( +"select DISTINCT(CHARACTER_SET_NAME) from information_schema.COLUMNS where CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND CHARACTER_SET_NAME IS NOT NULL" + ); + infoprint "Charsets for $dbinfo[0] database table column: " + . join( ', ', @distinct_column_charset ); + if ( scalar(@distinct_column_charset) > 1 ) { + badprint $dbinfo[0] + . " table column(s) has several charsets defined for all text like column(s)."; + push( @generalrec, + "Limit charset for column to one charset if possible for " + . $dbinfo[0] + . " database." ); + } + else { + goodprint $dbinfo[0] + . " table column(s) has same charset defined for all text like column(s)."; + } + + my @distinct_column_collation = select_array( +"select DISTINCT(COLLATION_NAME) from information_schema.COLUMNS where COLLATION_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND COLLATION_NAME IS NOT NULL" + ); + infoprint "Collations for $dbinfo[0] database table column: " + . join( ', ', @distinct_column_collation ); + if ( scalar(@distinct_column_collation) > 1 ) { + badprint $dbinfo[0] + . " table column(s) has several collations defined for all text like column(s)."; + push( @generalrec, + "Limit collations for column to one collation if possible for " + . $dbinfo[0] + . " database." ); + } + else { + goodprint $dbinfo[0] + . " table column(s) has same collation defined for all text like column(s)."; + } + } +} + +# Recommendations for database columns +sub mysql_tables { + return if ( $opt{tbstat} == 0 ); + + subheaderprint "Table Column Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint +"Skip Database metrics from information schema missing in this version"; + return; + } + if ( mysql_version_ge(8) and not mysql_version_eq(10) ) { + infoprint +"MySQL and Percona version 8 and greater have remove PROCEDURE ANALYSE feature"; + $opt{colstat} = 0; + infoprint "Disabling colstat parameter"; + + } + foreach ( select_user_dbs() ) { + my $dbname = $_; + next unless defined $_; + infoprint "Database: " . $_ . ""; + my @dbtable = select_array( +"SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbname' AND TABLE_TYPE='BASE TABLE' ORDER BY TABLE_NAME" + ); + foreach (@dbtable) { + my $tbname = $_; + infoprint " +-- TABLE: $tbname"; + infoprint " +-- TYPE: " + . select_one( +"SELECT ENGINE FROM information_schema.tables where TABLE_schema='$dbname' AND TABLE_NAME='$tbname'" + ); + + my $selIdxReq = <<"ENDSQL"; + SELECT index_name AS idxname, + GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols, + INDEX_TYPE as type + FROM information_schema.statistics + WHERE INDEX_SCHEMA='$dbname' + AND TABLE_NAME='$tbname' + GROUP BY idxname, type +ENDSQL + my @tbidx = select_array($selIdxReq); + my $found = 0; + foreach my $idx (@tbidx) { + my @info = split /\s/, $idx; + next if $info[0] eq 'NULL'; + infoprint + " +-- Index $info[0] - Cols: $info[1] - Type: $info[2]"; + $found++; + } + if ( $found == 0 ) { + badprint("Table $dbname.$tbname has no index defined"); + push @generalrec, + "Add at least a primary key on table $dbname.$tbname"; + } + my @tbcol = select_array( +"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname'" + ); + foreach (@tbcol) { + my $ctype = select_one( +"SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' " + ); + my $isnull = select_one( +"SELECT IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' " + ); + + my $current_type = + uc($ctype) . ( $isnull eq 'NO' ? " NOT NULL" : " NULL" ); + my $optimal_type = ''; + infoprint " +-- Column $tbname.$_: $current_type"; + if ( $opt{colstat} == 1 ) { + $optimal_type = select_str_g( "Optimal_fieldtype", +"SELECT \\`$_\\` FROM \\`$dbname\\`.\\`$tbname\\` PROCEDURE ANALYSE(100000)" + ) + unless ( mysql_version_ge(8) + and not mysql_version_eq(10) ); + } + if ( $optimal_type eq '' ) { + + #infoprint " +-- Current Fieldtype: $current_type"; + + #infoprint " Optimal Fieldtype: Not available"; + } + elsif ( $current_type ne $optimal_type + and $current_type !~ /.*DATETIME.*/ + and $current_type !~ /.*TIMESTAMP.*/ ) + { + infoprint " +-- Current Fieldtype: $current_type"; + if ( $optimal_type =~ /.*ENUM\(.*/ ) { + $optimal_type = "ENUM( ... )"; + } + infoprint " +-- Optimal Fieldtype: $optimal_type "; + if ( $optimal_type !~ /.*ENUM\(.*/ ) { + badprint +"Consider changing type for column $_ in table $dbname.$tbname"; + push( @generalrec, +"ALTER TABLE \`$dbname\`.\`$tbname\` MODIFY \`$_\` $optimal_type;" + ); + } + } + else { + goodprint "$dbname.$tbname ($_) type: $current_type"; + } + } + } + } +} + +# Recommendations for Indexes metrics +sub mysql_indexes { + return if ( $opt{idxstat} == 0 ); + + subheaderprint "Indexes Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint + "Skip Index metrics from information schema missing in this version"; + return; + } + +# unless ( mysql_version_ge( 5, 6 ) ) { +# infoprint +#"Skip Index metrics from information schema due to erroneous information provided in this version"; +# return; +# } + my $selIdxReq = <<'ENDSQL'; +SELECT + CONCAT(t.TABLE_SCHEMA, '.',t.TABLE_NAME) AS 'table', + CONCAT(s.INDEX_NAME, '(',s.COLUMN_NAME, ')') AS 'index' + , s.SEQ_IN_INDEX AS 'seq' + , s2.max_columns AS 'maxcol' + , s.CARDINALITY AS 'card' + , t.TABLE_ROWS AS 'est_rows' + , INDEX_TYPE as type + , ROUND(((s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) * 100), 2) AS 'sel' +FROM INFORMATION_SCHEMA.STATISTICS s + INNER JOIN INFORMATION_SCHEMA.TABLES t + ON s.TABLE_SCHEMA = t.TABLE_SCHEMA + AND s.TABLE_NAME = t.TABLE_NAME + INNER JOIN ( + SELECT + TABLE_SCHEMA + , TABLE_NAME + , INDEX_NAME + , MAX(SEQ_IN_INDEX) AS max_columns + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema') + AND INDEX_TYPE <> 'FULLTEXT' + GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME + ) AS s2 + ON s.TABLE_SCHEMA = s2.TABLE_SCHEMA + AND s.TABLE_NAME = s2.TABLE_NAME + AND s.INDEX_NAME = s2.INDEX_NAME +WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema') +AND t.TABLE_ROWS > 10 +AND s.CARDINALITY IS NOT NULL +AND (s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) < 8.00 +ORDER BY sel +LIMIT 10; +ENDSQL + my @idxinfo = select_array($selIdxReq); + infoprint "Worst selectivity indexes:"; + foreach (@idxinfo) { + debugprint "$_"; + my @info = split /\s/; + infoprint "Index: " . $info[1] . ""; + + infoprint " +-- COLUMN : " . $info[0] . ""; + infoprint " +-- NB SEQS : " . $info[2] . " sequence(s)"; + infoprint " +-- NB COLS : " . $info[3] . " column(s)"; + infoprint " +-- CARDINALITY : " . $info[4] . " distinct values"; + infoprint " +-- NB ROWS : " . $info[5] . " rows"; + infoprint " +-- TYPE : " . $info[6]; + infoprint " +-- SELECTIVITY : " . $info[7] . "%"; + + $result{'Indexes'}{ $info[1] }{'Column'} = $info[0]; + $result{'Indexes'}{ $info[1] }{'Sequence number'} = $info[2]; + $result{'Indexes'}{ $info[1] }{'Number of column'} = $info[3]; + $result{'Indexes'}{ $info[1] }{'Cardinality'} = $info[4]; + $result{'Indexes'}{ $info[1] }{'Row number'} = $info[5]; + $result{'Indexes'}{ $info[1] }{'Index Type'} = $info[6]; + $result{'Indexes'}{ $info[1] }{'Selectivity'} = $info[7]; + if ( $info[7] < 25 ) { + badprint "$info[1] has a low selectivity"; + } + } + infoprint "Indexes per database:"; + foreach my $dbname ( select_user_dbs() ) { + infoprint "Database: " . $dbname . ""; + $selIdxReq = <<"ENDSQL"; + SELECT concat(table_name,'.', index_name) AS idxname, + GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols, + SUM(CARDINALITY) as card, + INDEX_TYPE as type + FROM information_schema.statistics + WHERE INDEX_SCHEMA='$dbname' + AND index_name IS NOT NULL + GROUP BY table_name, idxname, type +ENDSQL + my $found = 0; + foreach my $idxinfo ( select_array($selIdxReq) ) { + my @info = split /\s/, $idxinfo; + next if $info[0] eq 'NULL'; + infoprint " +-- INDEX : " . $info[0]; + infoprint " +-- COLUMNS : " . $info[1]; + infoprint " +-- CARDINALITY: " . $info[2]; + infoprint " +-- TYPE : " . $info[4] if defined $info[4]; + infoprint " +-- COMMENT : " . $info[5] if defined $info[5]; + $found++; + } + badprint "No index found for $dbname database" if $found == 0; + push @generalrec, "Add indexes on tables from $dbname database" + if $found == 0; + } + return + unless ( defined( $myvar{'performance_schema'} ) + and $myvar{'performance_schema'} eq 'ON' ); + + $selIdxReq = <<'ENDSQL'; +SELECT CONCAT(object_schema,'.',object_name) AS 'table', index_name +FROM performance_schema.table_io_waits_summary_by_index_usage +WHERE index_name IS NOT NULL +AND count_star =0 +AND index_name <> 'PRIMARY' +AND object_schema NOT IN ( 'mysql', 'performance_schema', 'information_schema' ) +ORDER BY count_star, object_schema, object_name; +ENDSQL + @idxinfo = select_array($selIdxReq); + infoprint "Unused indexes:"; + push( @generalrec, "Remove unused indexes." ) if ( scalar(@idxinfo) > 0 ); + foreach (@idxinfo) { + debugprint "$_"; + my @info = split /\s/; + badprint "Index: $info[1] on $info[0] is not used."; + push @{ $result{'Indexes'}{'Unused Indexes'} }, + $info[0] . "." . $info[1]; + } +} + +sub mysql_views() { + subheaderprint "Views Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint + "Skip Index metrics from information schema missing in this version"; + return; + } +} + +sub mysql_routines() { + subheaderprint "Routines Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint + "Skip Index metrics from information schema missing in this version"; + return; + } +} + +sub mysql_triggers() { + subheaderprint "Triggers Metrics"; + unless ( mysql_version_ge( 5, 5 ) ) { + infoprint + "Skip Index metrics from information schema missing in this version"; + return; + } +} + +# Take the two recommendation arrays and display them at the end of the output +sub make_recommendations { + $result{'Recommendations'} = \@generalrec; + $result{'AdjustVariables'} = \@adjvars; + subheaderprint "Recommendations"; + if ( @generalrec > 0 ) { + prettyprint "General recommendations:"; + foreach (@generalrec) { prettyprint " " . $_ . ""; } + } + if ( @adjvars > 0 ) { + prettyprint "Variables to adjust:"; + if ( $mycalc{'pct_max_physical_memory'} > 90 ) { + prettyprint + " *** MySQL's maximum memory usage is dangerously high ***\n" + . " *** Add RAM before increasing MySQL buffer variables ***"; + } + foreach (@adjvars) { prettyprint " " . $_ . ""; } + } + if ( @generalrec == 0 && @adjvars == 0 ) { + prettyprint "No additional performance recommendations are available."; + } +} + +sub close_outputfile { + close($fh) if defined($fh); +} + +sub headerprint { + prettyprint + " >> MySQLTuner $tunerversion\n" + . "\t * Jean-Marie Renouard \n" + . "\t * Major Hayden \n" + . " >> Bug reports, feature requests, and downloads at http://mysqltuner.pl/\n" + . " >> Run with '--help' for additional options and output filtering"; +} + +sub string2file { + my $filename = shift; + my $content = shift; + open my $fh, q(>), $filename + or die +"Unable to open $filename in write mode. Please check permissions for this file or directory"; + print $fh $content if defined($content); + close $fh; + debugprint $content if ( $opt{'debug'} ); +} + +sub file2array { + my $filename = shift; + debugprint "* reading $filename" if ( $opt{'debug'} ); + my $fh; + open( $fh, q(<), "$filename" ) + or die "Couldn't open $filename for reading: $!\n"; + my @lines = <$fh>; + close($fh); + return @lines; +} + +sub file2string { + return join( '', file2array(@_) ); +} + +my $templateModel; +if ( $opt{'template'} ne 0 ) { + $templateModel = file2string( $opt{'template'} ); +} +else { + # DEFAULT REPORT TEMPLATE + $templateModel = <<'END_TEMPLATE'; + + + + MySQLTuner Report + + + + +

Result output

+
+{$data}
+
+ + + +END_TEMPLATE +} + +sub dump_result { + + #debugprint Dumper( \%result ) if ( $opt{'debug'} ); + debugprint "HTML REPORT: $opt{'reportfile'}"; + + if ( $opt{'reportfile'} ne 0 ) { + eval { require Text::Template }; + eval { require JSON }; + if ($@) { + badprint "Text::Template Module is needed."; + die "Text::Template Module is needed."; + } + + my $json = JSON->new->allow_nonref; + my $json_text = $json->pretty->encode( \%result ); + my %vars = ( + 'data' => \%result, + 'debug' => $json_text, + ); + my $template; + { + no warnings 'once'; + $template = Text::Template->new( + TYPE => 'STRING', + PREPEND => q{;}, + SOURCE => $templateModel, + DELIMITERS => [ '[%', '%]' ] + ) or die "Couldn't construct template: $Text::Template::ERROR"; + } + + open my $fh, q(>), $opt{'reportfile'} + or die +"Unable to open $opt{'reportfile'} in write mode. please check permissions for this file or directory"; + $template->fill_in( HASH => \%vars, OUTPUT => $fh ); + close $fh; + } + + if ( $opt{'json'} ne 0 ) { + eval { require JSON }; + if ($@) { + print "$bad JSON Module is needed.\n"; + return 1; + } + + my $json = JSON->new->allow_nonref; + print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + ->encode( \%result ); + + if ( $opt{'outputfile'} ne 0 ) { + unlink $opt{'outputfile'} if ( -e $opt{'outputfile'} ); + open my $fh, q(>), $opt{'outputfile'} + or die +"Unable to open $opt{'outputfile'} in write mode. please check permissions for this file or directory"; + print $fh $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + ->encode( \%result ); + close $fh; + } + } +} + +sub which { + my $prog_name = shift; + my $path_string = shift; + my @path_array = split /:/, $ENV{'PATH'}; + + for my $path (@path_array) { + return "$path/$prog_name" if ( -x "$path/$prog_name" ); + } + + return 0; +} + +# --------------------------------------------------------------------------- +# BEGIN 'MAIN' +# --------------------------------------------------------------------------- +headerprint; # Header Print + +validate_tuner_version; # Check last version +mysql_setup; # Gotta login first +debugprint "MySQL FINAL Client : $mysqlcmd $mysqllogin"; +debugprint "MySQL Admin FINAL Client : $mysqladmincmd $mysqllogin"; + +#exit(0); +os_setup; # Set up some OS variables +get_all_vars; # Toss variables/status into hashes +get_tuning_info; # Get information about the tuning connexion +validate_mysql_version; # Check current MySQL version + +check_architecture; # Suggest 64-bit upgrade +system_recommendations; # avoid to many service on the same host +log_file_recommendations; # check log file content +check_storage_engines; # Show enabled storage engines + +check_metadata_perf; # Show parameter impacting performance during analysis +mysql_databases; # Show informations about databases +mysql_tables; # Show informations about table column + +mysql_indexes; # Show informations about indexes +mysql_views; # Show informations about views +mysql_triggers; # Show informations about triggers +mysql_routines; # Show informations about routines +security_recommendations; # Display some security recommendations +cve_recommendations; # Display related CVE +calculations; # Calculate everything we need +mysql_stats; # Print the server stats +mysqsl_pfs; # Print Performance schema info +mariadb_threadpool; # Print MariaDB ThreadPool stats +mysql_myisam; # Print MyISAM stats +mysql_innodb; # Print InnoDB stats +mariadb_aria; # Print MariaDB Aria stats +mariadb_tokudb; # Print MariaDB Tokudb stats +mariadb_xtradb; # Print MariaDB XtraDB stats + +#mariadb_rockdb; # Print MariaDB RockDB stats +#mariadb_spider; # Print MariaDB Spider stats +#mariadb_connect; # Print MariaDB Connect stats +mariadb_galera; # Print MariaDB Galera Cluster stats +get_replication_status; # Print replication info +make_recommendations; # Make recommendations based on stats +dump_result; # Dump result if debug is on +close_outputfile; # Close reportfile if needed + +# --------------------------------------------------------------------------- +# END 'MAIN' +# --------------------------------------------------------------------------- +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + + MySQLTuner 1.9.9 - MySQL High Performance Tuning Script + +=head1 IMPORTANT USAGE GUIDELINES + +To run the script with the default options, run the script without arguments +Allow MySQL server to run for at least 24-48 hours before trusting suggestions +Some routines may require root level privileges (script will provide warnings) +You must provide the remote server's total memory when connecting to other servers + +=head1 CONNECTION AND AUTHENTICATION + + --host Connect to a remote host to perform tests (default: localhost) + --socket Use a different socket for a local connection + --port Port to use for connection (default: 3306) + --protocol tcp Force TCP connection instead of socket + --user Username to use for authentication + --userenv Name of env variable which contains username to use for authentication + --pass Password to use for authentication + --passenv Name of env variable which contains password to use for authentication + --ssl-ca Path to public key + --mysqladmin Path to a custom mysqladmin executable + --mysqlcmd Path to a custom mysql executable + --defaults-file Path to a custom .my.cnf + --server-log Path to explict log file (error_log) + +=head1 PERFORMANCE AND REPORTING OPTIONS + + --skipsize Don't enumerate tables and their types/sizes (default: on) + (Recommended for servers with many tables) + --json Print result as JSON string + --prettyjson Print result as JSON formatted string + --skippassword Don't perform checks on user passwords(default: off) + --checkversion Check for updates to MySQLTuner (default: don't check) + --updateversion Check for updates to MySQLTuner and update when newer version is available (default: don't check) + --forcemem Amount of RAM installed in megabytes + --forceswap Amount of swap memory configured in megabytes + --passwordfile Path to a password file list(one password by line) + --cvefile CVE File for vulnerability checks + --outputfile Path to a output txt file + --reportfile Path to a report txt file + --template Path to a template file + +=head1 OUTPUT OPTIONS + + --silent Don't output anything on screen + --verbose Prints out all options (default: no verbose, dbstat, idxstat, sysstat, tbstat, pfstat) + --nocolor Don't print output in color + --nogood Remove OK responses + --nobad Remove negative/suggestion responses + --noinfo Remove informational responses + --debug Print debug information + --noprocess Consider no other process is running + --dbstat Print database information + --nodbstat Don't Print database information + --tbstat Print table information + --notbstat Don't Print table information + --colstat Print column information + --nocolstat Don't Print column information + --idxstat Print index information + --noidxstat Don't Print index information + --sysstat Print system information + --nosysstat Don't Print system information + --pfstat Print Performance schema + --nopfstat Don't Print Performance schema + --bannedports Ports banned separated by comma(,) + --server-log Define specifi error_log to analyze + --maxportallowed Number of ports opened allowed on this hosts + --buffers Print global and per-thread buffer values + + +=head1 PERLDOC + +You can find documentation for this module with the perldoc command. + + perldoc mysqltuner + +=head2 INTERNALS + +L + + Internal documentation + +=head1 AUTHORS + +Major Hayden - major@mhtx.net + +=head1 CONTRIBUTORS + +=over 4 + +=item * + +Matthew Montgomery + +=item * + +Paul Kehrer + +=item * + +Dave Burgess + +=item * + +Jonathan Hinds + +=item * + +Mike Jackson + +=item * + +Nils Breunese + +=item * + +Shawn Ashlee + +=item * + +Luuk Vosslamber + +=item * + +Ville Skytta + +=item * + +Trent Hornibrook + +=item * + +Jason Gill + +=item * + +Mark Imbriaco + +=item * + +Greg Eden + +=item * + +Aubin Galinotti + +=item * + +Giovanni Bechis + +=item * + +Bill Bradford + +=item * + +Ryan Novosielski + +=item * + +Michael Scheidell + +=item * + +Blair Christensen + +=item * + +Hans du Plooy + +=item * + +Victor Trac + +=item * + +Everett Barnes + +=item * + +Tom Krouper + +=item * + +Gary Barrueto + +=item * + +Simon Greenaway + +=item * + +Adam Stein + +=item * + +Isart Montane + +=item * + +Baptiste M. + +=item * + +Cole Turner + +=item * + +Major Hayden + +=item * + +Joe Ashcraft + +=item * + +Jean-Marie Renouard + +=item * + +Stephan GroBberndt + +=item * + +Christian Loos + +=back + +=head1 SUPPORT + + +Bug reports, feature requests, and downloads at http://mysqltuner.pl/ + +Bug tracker can be found at https://github.com/major/MySQLTuner-perl/issues + +Maintained by Major Hayden (major\@mhtx.net) - Licensed under GPL + +=head1 SOURCE CODE + +L + + git clone https://github.com/major/MySQLTuner-perl.git + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006-2022 Major Hayden - major@mhtx.net + +For the latest updates, please visit http://mysqltuner.pl/ + +Git repository available at https://github.com/major/MySQLTuner-perl + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +=cut + +# Local variables: +# indent-tabs-mode: t +# cperl-indent-level: 8 +# perl-indent-level: 8 +# End: diff --git a/roles/services/files/scripts/promote-master.sh b/roles/services/files/scripts/promote-master.sh new file mode 100644 index 0000000..d4b5b9f --- /dev/null +++ b/roles/services/files/scripts/promote-master.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +mysql -e "SET GLOBAL event_scheduler = OFF" + +echo "SELECT db, name FROM mysql.event WHERE status = 'SLAVESIDE_DISABLED'" | mysql --raw --silent | \ +awk '{ + gsub("`", "``", $1); + gsub("`", "``", $2); + print "`"$1"`.`"$2"`"; +}' | \ +while read event; do + mysql -e "ALTER EVENT $event ENABLE" +done + +mysql -e "SET GLOBAL event_scheduler = ON" diff --git a/roles/services/files/scripts/promote-slave.sh b/roles/services/files/scripts/promote-slave.sh new file mode 100644 index 0000000..4856f4f --- /dev/null +++ b/roles/services/files/scripts/promote-slave.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +mysql -e "SET GLOBAL event_scheduler = OFF" + +echo "SELECT db, name FROM mysql.event WHERE status = 'ENABLED'" | mysql --raw --silent | \ +awk '{ + gsub("`", "``", $1); + gsub("`", "``", $2); + print "`"$1"`.`"$2"`"; +}' | \ +while read event; do + mysql -e "ALTER EVENT $event DISABLE ON SLAVE" +done + +mysql -e "SET GLOBAL event_scheduler = ON" + diff --git a/roles/services/files/scripts/scheduler-log.sh b/roles/services/files/scripts/scheduler-log.sh new file mode 100644 index 0000000..9cb0b37 --- /dev/null +++ b/roles/services/files/scripts/scheduler-log.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -e + +logFile="/var/log/mysql/error.log" +dateFile="/tmp/mysql_scheduler_log-lastdate" +logTable="util.eventLog" +purgeDays=30 + +quote() { + local str=${1//\'/\'\'/} + local str=${str//\\/\\\\} + echo "'$str'" +} + +mysql -e "SELECT TRUE" > /dev/null 2>&1 +if [ "$?" -ne "0" ]; then + exit +fi + +if [ -f "$dateFile" ]; then + fromDate=$(cat "$dateFile") +else + fromDate=0 +fi + +lastDate=$(tail -n1 "$logFile" | awk '{print $1" "$2}') +toDate=$(date +%s -d "$lastDate") + +awk -v fromDate="$fromDate" -v toDate="$toDate" '{ + split($1, date, "-"); + split($2, time, ":"); + timestamp = mktime(date[1]" "date[2]" "date[3]" "time[1]" "time[2]" "time[3]) + if (timestamp >= fromDate && timestamp < toDate && $4" "$5" "$6 == "[ERROR] Event Scheduler:") { + printf $1" "$2" "$7; + for (i=8; i<=NF; i++) printf FS $i ; + print ""; + } +}' "$logFile" | \ +\ +while read line; do + date="$(echo "$line" | cut -d' ' -f1,2)" + event="$(echo "$line" | cut -d' ' -f3)" + error="$(echo "$line" | cut -d' ' -f4-)" + echo "INSERT INTO $logTable (date, event, error)" \ + "VALUES ($(quote "$date"), $(quote "$event"), $(quote "$error"))" | mysql +done + +echo -n "$toDate" > "$dateFile" +echo "DELETE FROM $logTable WHERE date < TIMESTAMPADD(DAY, -$purgeDays, NOW())" | mysql diff --git a/roles/services/files/scripts/sync-conf.sh b/roles/services/files/scripts/sync-conf.sh new file mode 100644 index 0000000..3597234 --- /dev/null +++ b/roles/services/files/scripts/sync-conf.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +partner=root@db2.static.verdnatura.es +confDir=/etc/mysql/mariadb.conf.d +files=( + z90-vn.cnf + z95-production.cnf +) + +#echo "Reloading service." +#service mariadb reload + +if [ $? -eq "0" ]; then + echo "Synchronizing partner configuration." + for file in "${files[@]}"; do + scp "$confDir/$file" $partner:$confDir + done + + #echo "Reloading partner service." + #ssh $partner service mariadb reload +fi diff --git a/roles/services/files/z90-vn.cnf b/roles/services/files/z90-vn.cnf new file mode 100644 index 0000000..3b4c5cd --- /dev/null +++ b/roles/services/files/z90-vn.cnf @@ -0,0 +1,120 @@ +[mysqld] +# Docs: https://mariadb.com/kb/en/server-system-variables + +lc_messages = es_ES +lc_time_names = es_ES +character-set-server = utf8 +collation-server = utf8_unicode_ci +explicit_defaults_for_timestamp = ON +datadir = /var/lib/mysql +tmpdir = /mnt/mysqltmp +log_bin_trust_function_creators = 1 +sql_mode = NO_ENGINE_SUBSTITUTION +bind-address = 0.0.0.0 +max_password_errors = 50 + +#++++++++++++++++++++++++++++++++++++++++ Threads + +thread_stack = 512K +join_buffer_size = 2M +sort_buffer_size = 4M +net_buffer_length = 256K +max_allowed_packet = 16M +read_buffer_size = 1M +read_rnd_buffer_size = 512K + +#++++++++++++++++++++++++++++++++++++++++ Performance + +thread_cache_size = 450 +interactive_timeout = 1800 +wait_timeout = 1800 +open_files_limit = 20000 +low_priority_updates = 1 +table_open_cache = 40000 +table_definition_cache = 10000 +table_open_cache_instances = 1 +key_buffer_size = 256K +max_heap_table_size = 128M +tmp_table_size = 128M +concurrent_insert = ALWAYS +group_concat_max_len = 10000 +max_connect_errors = 50 + +#++++++++++++++++++++++++++++++++++++++++ Binary log + +log-bin = /mnt/mysqlbin/binlog/bin.log +max_binlog_size = 1GB +binlog_cache_size = 16M +binlog_stmt_cache_size = 16M +binlog_row_image = noblob +binlog_format = row +relay_log = /mnt/mysqlbin/binlog/relay.log + +binlog-ignore-db = tmp +binlog-ignore-db = PERCONA_SCHEMA + +#++++++++++++++++++++++++++++++++++++++++ Replication + +event-scheduler = ON +slave_exec_mode = STRICT + +replicate-ignore-db = tmp +replicate-ignore-table = util.eventLog +replicate-ignore-table = cache.cache_calc +replicate-ignore-table = cache.available +replicate-ignore-table = cache.availableNoRaids +replicate-ignore-table = cache.cache_valid +replicate-ignore-table = cache.stock +replicate-ignore-table = cache.visible + +#++++++++++++++++++++++++++++++++++++++++ InnoDB + +transaction-isolation = READ-COMMITTED +idle_transaction_timeout = 60 +innodb_io_capacity = 100 +innodb_io_capacity_max = 100 +innodb_monitor_enable = all +innodb_read_io_threads = 16 +innodb_write_io_threads = 16 +innodb_checksum_algorithm = crc32 +innodb_adaptive_hash_index = 0 +innodb_flush_method = O_DIRECT +innodb_log_buffer_size = 32M +innodb_log_file_size = 8G +innodb_purge_threads = 4 +innodb_buffer_pool_dump_at_shutdown = ON +innodb_buffer_pool_load_at_startup = ON + +#++++++++++++++++++++++++++++++++++++++++ Logging + +log_error = /var/log/mysql/error.log +log_warnings = 1 +log_output = TABLE +general_log = OFF +slow_query_log = ON +long_query_time = 2 +min_examined_row_limit = 0 +log_slow_admin_statements = ON +log_queries_not_using_indexes = OFF +max_error_count = 100 + +#++++++++++++++++++++++++++++++++++++++++ SSL + +ssl-ca = /etc/mysql/ca.pem +ssl-cert = /etc/mysql/cert.pem +ssl-key = /etc/mysql/key.pem + +#++++++++++++++++++++++++++++++++++++++++ Query cache + +query_cache_limit = 0 +query_cache_type = OFF +query_cache_size = 0 + +#++++++++++++++++++++++++++++++++++++++++ Performance Schema + +performance_schema = ON +performance_schema_digests_size = 20000 +performance-schema-consumer-events-statements-history = ON +performance_schema_consumer_events_statements_history_long = ON +query_response_time_stats = ON +userstat = ON diff --git a/roles/services/files/z95-production.cnf b/roles/services/files/z95-production.cnf new file mode 100644 index 0000000..17f9732 --- /dev/null +++ b/roles/services/files/z95-production.cnf @@ -0,0 +1,6 @@ +[mysqld] + +port = 3306 +max_connections = 1000 +expire_logs_days = 7 +innodb_buffer_pool_size = 64G diff --git a/roles/services/files/z99-local.cnf b/roles/services/files/z99-local.cnf new file mode 100644 index 0000000..58249ee --- /dev/null +++ b/roles/services/files/z99-local.cnf @@ -0,0 +1,7 @@ +[mysqld] + +server-id = 1 +#bind-address = 127.0.0.1 +#event-scheduler = OFF +#skip-log-bin +#skip-slave-start diff --git a/roles/services/handlers/main.yml b/roles/services/handlers/main.yml index a1957d8..1769d19 100644 --- a/roles/services/handlers/main.yml +++ b/roles/services/handlers/main.yml @@ -2,4 +2,7 @@ systemd: name: chrony state: restarted +- name: restart-mariadb + systemd: + name: mariadb diff --git a/roles/services/tasks/main.yml b/roles/services/tasks/main.yml index c57d667..5315ca9 100644 --- a/roles/services/tasks/main.yml +++ b/roles/services/tasks/main.yml @@ -1,2 +1,4 @@ - import_tasks: timeserver.yml tags: timeserver +- import_tasks: mariadb.yml + tags: mariadb diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml new file mode 100644 index 0000000..07ccb6e --- /dev/null +++ b/roles/services/tasks/mariadb.yml @@ -0,0 +1,41 @@ +- name: Install MariaDB packages + apt: + name: mariadb-server + state: present + #install_recommends: no +- name: Set MariaDB custom configuration + copy: + src: "{{ item }}" + dest: /etc/mysql/mariadb.conf.d/ + owner: root + group: root + mode: u=rw,g=r,o=r + with_fileglob: + - "files/z9*.cnf" + notify: restart-mariadb +- name: Set MariaDB custom service.d override.conf + copy: + src: mariadb_override.conf + dest: /etc/systemd/system/mariadb.service.d/override.conf + owner: root + group: root + mode: u=rw,g=r,o=r + notify: restart-mariadb +- name: Set MariaDB custom root scripts + copy: + src: "{{ item }}" + dest: /root/scripts + owner: root + group: root + mode: u=rwx,g=rx,o=rx + with_fileglob: + - "files/scripts/*.sh" + notify: restart-mariadb +- name: Set MariaDB README root scripts + copy: + src: files/scripts/README.md + dest: /root/scripts/README.md + owner: root + group: root + mode: u=rw,g=r,o=r + notify: restart-mariadb From 6977f293e9cec1f29b5ec313637c131035c16335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Fri, 29 Nov 2024 10:51:36 +0100 Subject: [PATCH 10/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 34 +++++++++++++++++++++++++-- roles/services/templates/cron_mariadb | 4 ++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 roles/services/templates/cron_mariadb diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 07ccb6e..651ce84 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -13,6 +13,13 @@ with_fileglob: - "files/z9*.cnf" notify: restart-mariadb +- name: Ensure mariadb service override directory exists + file: + path: /etc/systemd/system/mariadb.service.d + state: directory + owner: root + group: root + mode: '0755' - name: Set MariaDB custom service.d override.conf copy: src: mariadb_override.conf @@ -21,17 +28,24 @@ group: root mode: u=rw,g=r,o=r notify: restart-mariadb +- name: Ensure scripts root directory exists + file: + path: /root/scripts + state: directory + owner: root + group: root + mode: '0755' - name: Set MariaDB custom root scripts copy: src: "{{ item }}" - dest: /root/scripts + dest: /root/scripts/ owner: root group: root mode: u=rwx,g=rx,o=rx with_fileglob: - "files/scripts/*.sh" notify: restart-mariadb -- name: Set MariaDB README root scripts +- name: Set MariaDB README root script copy: src: files/scripts/README.md dest: /root/scripts/README.md @@ -39,3 +53,19 @@ group: root mode: u=rw,g=r,o=r notify: restart-mariadb +- name: Set MariaDB performance and customize root script + copy: + src: files/scripts/mysqltuner.pl + dest: /root/scripts/mysqltuner.pl + owner: root + group: root + mode: u=rwx,g=rx,o=rx + notify: restart-mariadb +- name: Set MariaDB Cron to /etc/cron.d + copy: + src: templates/cron_mariadb + dest: /etc/cron.d/vn + owner: root + group: root + mode: u=rw,g=r,o=r + notify: restart-mariadb \ No newline at end of file diff --git a/roles/services/templates/cron_mariadb b/roles/services/templates/cron_mariadb new file mode 100644 index 0000000..bc281bd --- /dev/null +++ b/roles/services/templates/cron_mariadb @@ -0,0 +1,4 @@ +MAILTO="{{ sysadmin_mail }}" + +*/15 * * * * root /root/scripts/check-memory.sh +*/30 * * * * root /root/scripts/scheduler-log.sh From 3c2302ec390919d4cd583453f6b82075439acba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Fri, 29 Nov 2024 13:11:10 +0100 Subject: [PATCH 11/41] Refs #8140: MariaDB Server Deploy - Role WIP --- .../files/mariabackup/bacula-before.sh | 30 +++++++++++ roles/services/files/mariabackup/config.sh | 20 +++++++ .../services/files/mariabackup/inc-backup.sh | 38 +++++++++++++ roles/services/files/mariabackup/my.cnf | 7 +++ .../files/mariabackup/restore-backup.sh | 53 +++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 roles/services/files/mariabackup/bacula-before.sh create mode 100644 roles/services/files/mariabackup/config.sh create mode 100644 roles/services/files/mariabackup/inc-backup.sh create mode 100644 roles/services/files/mariabackup/my.cnf create mode 100644 roles/services/files/mariabackup/restore-backup.sh diff --git a/roles/services/files/mariabackup/bacula-before.sh b/roles/services/files/mariabackup/bacula-before.sh new file mode 100644 index 0000000..f1628fd --- /dev/null +++ b/roles/services/files/mariabackup/bacula-before.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# https://mariadb.com/kb/en/mariabackup/ +set -e + +myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$myDir/config.sh" + +todayDir="$(date +%Y-%m-%d)" +backupName="${todayDir}_$(date +"%H-%M")_full" +backupFile="$backupDir/$backupName.gz" + +if [ -d "$backupDir" ]; then + rm -rf "$backupDir/"* +fi + +ulimit -n 8192 +mariabackup \ + --defaults-extra-file="$myDir/my.cnf" \ + --backup \ + --extra-lsndir="$backupDir/$backupName" \ + --history="$todayDir" \ + 2>> "$logFile" \ + | gzip \ + > "$backupFile" + +if [ $? != "0" ]; then + echo "An error ocurred during backup, please take a look at log file: $logFile" + exit 1 +fi + diff --git a/roles/services/files/mariabackup/config.sh b/roles/services/files/mariabackup/config.sh new file mode 100644 index 0000000..bb61fd8 --- /dev/null +++ b/roles/services/files/mariabackup/config.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Destination file for backup logs +logFile=/var/log/vn-mariabackup.log + +# Temporary local directory to save backups +backupDir=/mnt/local-backup + +# Directory for backup history +historyDir=/mnt/backup4mariadb + +# Number of days for backup rotation +cleanDays=90 + +# Directory for temporal restore data +restoreDir=/mnt/mysqldata/mysql-restore + +# Directory of MySQL data +dataDir=/mnt/mysqldata/mysql + diff --git a/roles/services/files/mariabackup/inc-backup.sh b/roles/services/files/mariabackup/inc-backup.sh new file mode 100644 index 0000000..c6d6e91 --- /dev/null +++ b/roles/services/files/mariabackup/inc-backup.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# https://mariadb.com/kb/en/incremental-backup-and-restore-with-mariabackup/ +set -e + +myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$myDir/config.sh" + +todayDir="$(date +%Y-%m-%d)" +todayPath="$historyDir/$todayDir" + +pattern="$todayPath/${todayDir}_??-??_full.xb.gz.enc" +files=($pattern) +backupFile="${files[0]}" +backupBase=$(basename -- "$backupFile") +backupName="${backupBase%%.*}" + +incrementalName="${todayDir}_$(date +"%H-%M")_incremental" +incrementalFile="$backupDir/${incrementalName}.xb.gz.enc" + +ulimit -n 24098 +mariabackup \ + --defaults-extra-file="$myDir/my.cnf" \ + --backup \ + --incremental-basedir="$backupDir/$backupName" \ + --extra-lsndir="$backupDir/$incrementalName" \ + --incremental-history-name="$todayDir" \ + 2>> "$logFile" \ + | gzip \ + | openssl enc -aes-256-cbc -pbkdf2 -kfile "$myDir/xbcrypt.key" \ + > "$incrementalFile" + +if [ $? != "0" ]; then + echo "An error ocurred during backup, please take a look at log file: $logFile" + exit 1 +fi + +cp "$incrementalFile" "$todayPath" +cp -r "$backupDir/$incrementalName" "$todayPath" diff --git a/roles/services/files/mariabackup/my.cnf b/roles/services/files/mariabackup/my.cnf new file mode 100644 index 0000000..14edb89 --- /dev/null +++ b/roles/services/files/mariabackup/my.cnf @@ -0,0 +1,7 @@ +[mariabackup] +host = localhost +user = {{ user_mariabackup }} +password = {{ password_user_mariabackup }} +use-memory = 1G +parallel = 2 +stream = mbstream diff --git a/roles/services/files/mariabackup/restore-backup.sh b/roles/services/files/mariabackup/restore-backup.sh new file mode 100644 index 0000000..0673526 --- /dev/null +++ b/roles/services/files/mariabackup/restore-backup.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# https://mariadb.com/kb/en/using-encryption-and-compression-tools-with-mariabackup/ +set -e + +myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$myDir/config.sh" + +backupFile=$1 + +if [ -z "$backupFile" ]; then + echo "Backup file not defined." + exit 1 +fi + +if [ ! -f "$backupFile" ]; then + echo "Backup file does not exist: $backupFile" + exit 2 +fi + +echo "Restoring MySQL data from backup." + +rm -rf "$restoreDir" +mkdir -p "$restoreDir" + +echo "Decompresing backup." +gzip --decompress --stdout "$backupFile" \ + | mbstream -x --directory="$restoreDir" + +echo "Preparing backup." +mariabackup \ + --defaults-extra-file="$myDir/my.cnf" \ + --prepare \ + --target-dir="$restoreDir" + +echo "Stopping service." +service mariadb stop +if pgrep mariadbd; then pkill -9 mariadbd; fi + +echo "Restoring data." +rm -rf "$dataDir" +mariabackup \ + --defaults-extra-file="$myDir/my.cnf" \ + --move-back \ + --target-dir="$restoreDir" \ + 2>> "$logFile" +chown -R mysql:mysql "$dataDir" + +rm "$dataDir/mysql/slow_log."* +rm "$dataDir/mysql/general_log."* + +echo "Removing restore data." +rm -r "$restoreDir" + From df27599f14d7b12a227d89ee663a7dc5bc9f7c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 10 Dec 2024 13:14:52 +0100 Subject: [PATCH 12/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 651ce84..4effaf6 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -62,7 +62,7 @@ mode: u=rwx,g=rx,o=rx notify: restart-mariadb - name: Set MariaDB Cron to /etc/cron.d - copy: + template: src: templates/cron_mariadb dest: /etc/cron.d/vn owner: root From 69180761c8111e3a27cdaccfb5ff0e4a3b7721c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 10 Dec 2024 15:47:01 +0100 Subject: [PATCH 13/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 4effaf6..88f2d1f 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -19,7 +19,7 @@ state: directory owner: root group: root - mode: '0755' + mode: u=rwx,g=rx,o=rx - name: Set MariaDB custom service.d override.conf copy: src: mariadb_override.conf @@ -34,7 +34,7 @@ state: directory owner: root group: root - mode: '0755' + mode: u=rwx,g=rx,o=rx - name: Set MariaDB custom root scripts copy: src: "{{ item }}" @@ -61,6 +61,14 @@ group: root mode: u=rwx,g=rx,o=rx notify: restart-mariadb +- name: Ensure log directory exists /var/log/mysql + file: + path: /var/log/mysql + state: directory + owner: mysql + group: adm + mode: u=rwx,g=rxs,o= + notify: restart-mariadb - name: Set MariaDB Cron to /etc/cron.d template: src: templates/cron_mariadb From 506fd6b341dea7890f1291c85292fc6378187a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 11 Dec 2024 10:54:04 +0100 Subject: [PATCH 14/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/handlers/main.yml | 3 +++ roles/services/tasks/mariadb.yml | 46 +++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/roles/services/handlers/main.yml b/roles/services/handlers/main.yml index 1769d19..c5b90a5 100644 --- a/roles/services/handlers/main.yml +++ b/roles/services/handlers/main.yml @@ -5,4 +5,7 @@ - name: restart-mariadb systemd: name: mariadb +- name: reload systemd + command: + cmd: systemctl daemon-reload diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 88f2d1f..9ee1c88 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -3,6 +3,31 @@ name: mariadb-server state: present #install_recommends: no + +- name: Ensure log directory exists /var/log/mysql + file: + path: /var/log/mysql + state: directory + owner: mysql + group: adm + mode: u=rwx,g=rxs,o= + +- name: Ensure mysqlbin directory exists /mnt/mysqlbin + file: + path: /mnt/mysqlbin + state: directory + owner: root + group: root + mode: u=rwx,g=rx,o=rx + +- name: Ensure mysqltmp directory exists /mnt/mysqltmp with sticky bit + file: + path: /mnt/mysqltmp + state: directory + mode: u=rwx,g=rwx,o=rwxt + owner: root + group: root + - name: Set MariaDB custom configuration copy: src: "{{ item }}" @@ -13,6 +38,7 @@ with_fileglob: - "files/z9*.cnf" notify: restart-mariadb + - name: Ensure mariadb service override directory exists file: path: /etc/systemd/system/mariadb.service.d @@ -20,6 +46,7 @@ owner: root group: root mode: u=rwx,g=rx,o=rx + - name: Set MariaDB custom service.d override.conf copy: src: mariadb_override.conf @@ -27,7 +54,8 @@ owner: root group: root mode: u=rw,g=r,o=r - notify: restart-mariadb + notify: reload systemd + - name: Ensure scripts root directory exists file: path: /root/scripts @@ -35,6 +63,7 @@ owner: root group: root mode: u=rwx,g=rx,o=rx + - name: Set MariaDB custom root scripts copy: src: "{{ item }}" @@ -44,7 +73,7 @@ mode: u=rwx,g=rx,o=rx with_fileglob: - "files/scripts/*.sh" - notify: restart-mariadb + - name: Set MariaDB README root script copy: src: files/scripts/README.md @@ -52,7 +81,7 @@ owner: root group: root mode: u=rw,g=r,o=r - notify: restart-mariadb + - name: Set MariaDB performance and customize root script copy: src: files/scripts/mysqltuner.pl @@ -60,15 +89,7 @@ owner: root group: root mode: u=rwx,g=rx,o=rx - notify: restart-mariadb -- name: Ensure log directory exists /var/log/mysql - file: - path: /var/log/mysql - state: directory - owner: mysql - group: adm - mode: u=rwx,g=rxs,o= - notify: restart-mariadb + - name: Set MariaDB Cron to /etc/cron.d template: src: templates/cron_mariadb @@ -76,4 +97,3 @@ owner: root group: root mode: u=rw,g=r,o=r - notify: restart-mariadb \ No newline at end of file From b1410f91a0921efb293aa2a2e39d7f072e7d61ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 11 Dec 2024 12:04:37 +0100 Subject: [PATCH 15/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 101 +++++++++++-------------------- 1 file changed, 36 insertions(+), 65 deletions(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 9ee1c88..295afb7 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -4,29 +4,20 @@ state: present #install_recommends: no -- name: Ensure log directory exists /var/log/mysql +- name: Ensure required directories exist file: - path: /var/log/mysql + path: "{{ item.path }}" state: directory - owner: mysql - group: adm - mode: u=rwx,g=rxs,o= - -- name: Ensure mysqlbin directory exists /mnt/mysqlbin - file: - path: /mnt/mysqlbin - state: directory - owner: root - group: root - mode: u=rwx,g=rx,o=rx - -- name: Ensure mysqltmp directory exists /mnt/mysqltmp with sticky bit - file: - path: /mnt/mysqltmp - state: directory - mode: u=rwx,g=rwx,o=rwxt - owner: root - group: root + owner: "{{ item.owner }}" + group: "{{ item.group }}" + mode: "{{ item.mode }}" + loop: + - { path: /var/log/mysql, owner: mysql, group: adm, mode: 'u=rwx,g=rxs,o=' } + - { path: /root/scripts, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /mnt/mysqltmp, owner: root, group: root, mode: 'u=rwx,g=rwx,o=rwxt' } + - { path: /mnt/mysqlbin/binlog, owner: mysql, group: mysql, mode: 'u=rwx,g=,o=' } - name: Set MariaDB custom configuration copy: @@ -39,31 +30,6 @@ - "files/z9*.cnf" notify: restart-mariadb -- name: Ensure mariadb service override directory exists - file: - path: /etc/systemd/system/mariadb.service.d - state: directory - owner: root - group: root - mode: u=rwx,g=rx,o=rx - -- name: Set MariaDB custom service.d override.conf - copy: - src: mariadb_override.conf - dest: /etc/systemd/system/mariadb.service.d/override.conf - owner: root - group: root - mode: u=rw,g=r,o=r - notify: reload systemd - -- name: Ensure scripts root directory exists - file: - path: /root/scripts - state: directory - owner: root - group: root - mode: u=rwx,g=rx,o=rx - - name: Set MariaDB custom root scripts copy: src: "{{ item }}" @@ -74,26 +40,31 @@ with_fileglob: - "files/scripts/*.sh" -- name: Set MariaDB README root script - copy: - src: files/scripts/README.md - dest: /root/scripts/README.md +- name: Ensure required files are copied to their destinations + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" owner: root group: root - mode: u=rw,g=r,o=r + mode: "{{ item.mode }}" + loop: + - { src: 'files/scripts/README.md', dest: '/root/scripts/README.md', mode: 'u=rw,g=r,o=r' } + - { src: 'mariadb_override.conf', dest: '/etc/systemd/system/mariadb.service.d/override.conf', mode: 'u=rw,g=r,o=r' } + - { src: 'files/scripts/mysqltuner.pl', dest: '/root/scripts/mysqltuner.pl', mode: 'u=rwx,g=rx,o=rx' } + notify: + - reload systemd -- name: Set MariaDB performance and customize root script - copy: - src: files/scripts/mysqltuner.pl - dest: /root/scripts/mysqltuner.pl - owner: root - group: root - mode: u=rwx,g=rx,o=rx +#- name: Set MariaDB Cron to /etc/cron.d +# template: +# src: templates/cron_mariadb +# dest: /etc/cron.d/vn +# owner: root +# group: root +# mode: u=rw,g=r,o=r -- name: Set MariaDB Cron to /etc/cron.d - template: - src: templates/cron_mariadb - dest: /etc/cron.d/vn - owner: root - group: root - mode: u=rw,g=r,o=r +- name: Add tmpfs in /etc/fstab + blockinfile: + path: /etc/fstab + marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" + block: | + tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 From 6d8ecfb5892bb6d7d2bd7ebcf525848bd17de8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 11 Dec 2024 13:50:12 +0100 Subject: [PATCH 16/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 295afb7..619c795 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -68,3 +68,15 @@ marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" block: | tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 + +- name: Insert MySQL certificates + copy: + content: "{{ item.content }}" + dest: "{{ item.dest }}" + owner: mysql + group: mysql + mode: "{{ item.mode }}" + loop: + - { content: '{{ ca_mysql }}', dest: '/etc/mysql/ca.pem', mode: 'u=rw,g=r,o=r' } + - { content: '{{ cert_mysql }}', dest: '/etc/mysql/cert.pem', mode: 'u=rw,g=r,o=r' } + - { content: '{{ private_mysql }}', dest: '/etc/mysql/key.pem', mode: 'u=rw,g=,o=' } From f8e3906a5bcbdf9a31c3285228c93d62f47b975b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 11 Dec 2024 13:50:50 +0100 Subject: [PATCH 17/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 619c795..02ded8c 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -54,13 +54,13 @@ notify: - reload systemd -#- name: Set MariaDB Cron to /etc/cron.d -# template: -# src: templates/cron_mariadb -# dest: /etc/cron.d/vn -# owner: root -# group: root -# mode: u=rw,g=r,o=r +- name: Set MariaDB Cron to /etc/cron.d + template: + src: templates/cron_mariadb + dest: /etc/cron.d/vn + owner: root + group: root + mode: u=rw,g=r,o=r - name: Add tmpfs in /etc/fstab blockinfile: From 2cf5358d614fdca4954d354f69bbd28cd7992766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 11 Dec 2024 14:05:51 +0100 Subject: [PATCH 18/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/tasks/mariadb.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 02ded8c..8b1bde2 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -3,7 +3,6 @@ name: mariadb-server state: present #install_recommends: no - - name: Ensure required directories exist file: path: "{{ item.path }}" @@ -18,7 +17,6 @@ - { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - { path: /mnt/mysqltmp, owner: root, group: root, mode: 'u=rwx,g=rwx,o=rwxt' } - { path: /mnt/mysqlbin/binlog, owner: mysql, group: mysql, mode: 'u=rwx,g=,o=' } - - name: Set MariaDB custom configuration copy: src: "{{ item }}" @@ -29,7 +27,6 @@ with_fileglob: - "files/z9*.cnf" notify: restart-mariadb - - name: Set MariaDB custom root scripts copy: src: "{{ item }}" @@ -39,7 +36,6 @@ mode: u=rwx,g=rx,o=rx with_fileglob: - "files/scripts/*.sh" - - name: Ensure required files are copied to their destinations ansible.builtin.copy: src: "{{ item.src }}" @@ -53,7 +49,6 @@ - { src: 'files/scripts/mysqltuner.pl', dest: '/root/scripts/mysqltuner.pl', mode: 'u=rwx,g=rx,o=rx' } notify: - reload systemd - - name: Set MariaDB Cron to /etc/cron.d template: src: templates/cron_mariadb @@ -61,14 +56,12 @@ owner: root group: root mode: u=rw,g=r,o=r - - name: Add tmpfs in /etc/fstab blockinfile: path: /etc/fstab marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" block: | tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 - - name: Insert MySQL certificates copy: content: "{{ item.content }}" From a58386259c149f04d53a15a51f6e618d0271f356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Mon, 16 Dec 2024 15:42:53 +0100 Subject: [PATCH 19/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/defaults/main.yaml | 8 +- roles/services/files/mariadb_override.conf | 1 + roles/services/files/scripts/check-memory.sh | 0 roles/services/files/scripts/export-privs.sh | 0 roles/services/files/scripts/mysqltuner.pl | 0 .../services/files/scripts/promote-master.sh | 0 roles/services/files/scripts/promote-slave.sh | 0 roles/services/files/scripts/scheduler-log.sh | 0 roles/services/files/scripts/sync-conf.sh | 0 roles/services/files/z90-vn.cnf | 1 - roles/services/handlers/main.yml | 8 +- roles/services/tasks/mariadb.yml | 98 ++++++++++++------- 12 files changed, 77 insertions(+), 39 deletions(-) mode change 100644 => 100755 roles/services/files/scripts/check-memory.sh mode change 100644 => 100755 roles/services/files/scripts/export-privs.sh mode change 100644 => 100755 roles/services/files/scripts/mysqltuner.pl mode change 100644 => 100755 roles/services/files/scripts/promote-master.sh mode change 100644 => 100755 roles/services/files/scripts/promote-slave.sh mode change 100644 => 100755 roles/services/files/scripts/scheduler-log.sh mode change 100644 => 100755 roles/services/files/scripts/sync-conf.sh diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index 139597f..c326c4f 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -1,2 +1,6 @@ - - +mariadb_base_packages: + - mariadb-server + - mariadb-backup +mariadb_requeriments: + - curl + - apt-transport-https \ No newline at end of file diff --git a/roles/services/files/mariadb_override.conf b/roles/services/files/mariadb_override.conf index 678e16a..215c342 100644 --- a/roles/services/files/mariadb_override.conf +++ b/roles/services/files/mariadb_override.conf @@ -1,3 +1,4 @@ [Service] LimitNOFILE=600000 LimitMEMLOCK=2M +LimitNOFILE=81035 \ No newline at end of file diff --git a/roles/services/files/scripts/check-memory.sh b/roles/services/files/scripts/check-memory.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/export-privs.sh b/roles/services/files/scripts/export-privs.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/mysqltuner.pl b/roles/services/files/scripts/mysqltuner.pl old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/promote-master.sh b/roles/services/files/scripts/promote-master.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/promote-slave.sh b/roles/services/files/scripts/promote-slave.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/scheduler-log.sh b/roles/services/files/scripts/scheduler-log.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/scripts/sync-conf.sh b/roles/services/files/scripts/sync-conf.sh old mode 100644 new mode 100755 diff --git a/roles/services/files/z90-vn.cnf b/roles/services/files/z90-vn.cnf index 3b4c5cd..0f6a716 100644 --- a/roles/services/files/z90-vn.cnf +++ b/roles/services/files/z90-vn.cnf @@ -116,5 +116,4 @@ performance_schema = ON performance_schema_digests_size = 20000 performance-schema-consumer-events-statements-history = ON performance_schema_consumer_events_statements_history_long = ON -query_response_time_stats = ON userstat = ON diff --git a/roles/services/handlers/main.yml b/roles/services/handlers/main.yml index c5b90a5..77780d8 100644 --- a/roles/services/handlers/main.yml +++ b/roles/services/handlers/main.yml @@ -2,10 +2,12 @@ systemd: name: chrony state: restarted -- name: restart-mariadb - systemd: - name: mariadb - name: reload systemd command: cmd: systemctl daemon-reload +- name: restart-mariadb + systemd: + name: mariadb + state: restarted + diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 8b1bde2..514f8b4 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,8 +1,29 @@ +# Percona things pmm2-client https://docs.percona.com/percona-monitoring-and-management/setting-up/client/index.html#package-manager +# Add backup directory custom scripts + +- name: Ensure Install requirements for MariaDB repository setup script + apt: + name: "{{ mariadb_requeriments }}" + state: present + install_recommends: no + +- name: Download MariaDB repository setup script + get_url: + url: "https://r.mariadb.com/downloads/mariadb_repo_setup" + dest: "/tmp/mariadb_repo_setup" + mode: "u=rwx,g=rx,o=rx" + +- name: Run MariaDB repository setup script + command: + cmd: "/bin/bash /tmp/mariadb_repo_setup --mariadb-server-version=10.11.10" + creates: "/etc/apt/sources.list.d/mariadb.list" + - name: Install MariaDB packages apt: - name: mariadb-server + name: "{{ mariadb_base_packages }}" state: present - #install_recommends: no + install_recommends: no + - name: Ensure required directories exist file: path: "{{ item.path }}" @@ -11,33 +32,16 @@ group: "{{ item.group }}" mode: "{{ item.mode }}" loop: - - { path: /var/log/mysql, owner: mysql, group: adm, mode: 'u=rwx,g=rxs,o=' } - - { path: /root/scripts, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /mnt/local-backup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - - { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - { path: /mnt/mysqltmp, owner: root, group: root, mode: 'u=rwx,g=rwx,o=rwxt' } - { path: /mnt/mysqlbin/binlog, owner: mysql, group: mysql, mode: 'u=rwx,g=,o=' } -- name: Set MariaDB custom configuration - copy: - src: "{{ item }}" - dest: /etc/mysql/mariadb.conf.d/ - owner: root - group: root - mode: u=rw,g=r,o=r - with_fileglob: - - "files/z9*.cnf" - notify: restart-mariadb -- name: Set MariaDB custom root scripts - copy: - src: "{{ item }}" - dest: /root/scripts/ - owner: root - group: root - mode: u=rwx,g=rx,o=rx - with_fileglob: - - "files/scripts/*.sh" + - { path: /var/log/mysql, owner: mysql, group: adm, mode: 'u=rwx,g=rxs,o=' } + - { path: /root/scripts, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx'} + - { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - name: Ensure required files are copied to their destinations - ansible.builtin.copy: + copy: src: "{{ item.src }}" dest: "{{ item.dest }}" owner: root @@ -47,8 +51,29 @@ - { src: 'files/scripts/README.md', dest: '/root/scripts/README.md', mode: 'u=rw,g=r,o=r' } - { src: 'mariadb_override.conf', dest: '/etc/systemd/system/mariadb.service.d/override.conf', mode: 'u=rw,g=r,o=r' } - { src: 'files/scripts/mysqltuner.pl', dest: '/root/scripts/mysqltuner.pl', mode: 'u=rwx,g=rx,o=rx' } - notify: - - reload systemd + notify: reload systemd + +- name: Set MariaDB custom root scripts + copy: + src: "{{ item }}" + dest: /root/scripts/ + owner: root + group: root + mode: u=rwx,g=rx,o=rx + with_fileglob: + - "files/scripts/*.sh" + +- name: Add tmpfs in /etc/fstab + blockinfile: + path: /etc/fstab + marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" + block: | + tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 + +- name: Mount all filesystems from /etc/fstab + command: mount -a + #when: ansible_facts.mounts | selectattr('mount', 'equalto', '/mnt/mysqltmp') | list | length == 0 + - name: Set MariaDB Cron to /etc/cron.d template: src: templates/cron_mariadb @@ -56,12 +81,7 @@ owner: root group: root mode: u=rw,g=r,o=r -- name: Add tmpfs in /etc/fstab - blockinfile: - path: /etc/fstab - marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" - block: | - tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 + - name: Insert MySQL certificates copy: content: "{{ item.content }}" @@ -73,3 +93,15 @@ - { content: '{{ ca_mysql }}', dest: '/etc/mysql/ca.pem', mode: 'u=rw,g=r,o=r' } - { content: '{{ cert_mysql }}', dest: '/etc/mysql/cert.pem', mode: 'u=rw,g=r,o=r' } - { content: '{{ private_mysql }}', dest: '/etc/mysql/key.pem', mode: 'u=rw,g=,o=' } + notify: restart-mariadb + +- name: Set MariaDB custom configuration + copy: + src: "{{ item }}" + dest: /etc/mysql/mariadb.conf.d/ + owner: root + group: root + mode: u=rw,g=r,o=r + with_fileglob: + - "files/z9*.cnf" + notify: restart-mariadb \ No newline at end of file From 5cac63b6f046c9c113b3ac4e437bd89aded6586f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 12:53:59 +0100 Subject: [PATCH 20/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/defaults/main.yaml | 25 ++++++++++++++++- roles/services/tasks/mariadb.yml | 46 ++++++++++++------------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index c326c4f..646f9e9 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -1,6 +1,29 @@ mariadb_base_packages: - mariadb-server - mariadb-backup + - pmm2-client mariadb_requeriments: - curl - - apt-transport-https \ No newline at end of file + - apt-transport-https +required_directories: + - { path: /mnt/local-backup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /mnt/mysqltmp, owner: root, group: root, mode: 'u=rwx,g=rwx,o=rwxt' } + - { path: /mnt/mysqlbin/binlog, owner: mysql, group: mysql, mode: 'u=rwx,g=,o=' } + - { path: /root/scripts, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + - { path: /root/mariabackup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } +required_files_and_mariabackup_files_and_root_scripts: + - { src: "mariadb_override.conf", dest: "/etc/systemd/system/mariadb.service.d/override.conf", mode: "u=rw,g=r,o=r" } + - { src: "files/mariabackup/bacula-before.sh", dest: "/root/mariabackup/bacula-before.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/mariabackup/config.sh", dest: "/root/mariabackup/config.sh", mode: "u=rwx,g=rx,o=x" } + - { src: "files/mariabackup/inc-backup.sh", dest: "/root/mariabackup/inc-backup.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/mariabackup/my.cnf", dest: "/root/mariabackup/my.cnf", mode: "u=rw,g=,o=" } + - { src: "files/mariabackup/restore-backup.sh", dest: "/root/mariabackup/restore-backup.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/check-memory.sh", dest: "/root/scripts/check-memory.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/export-privs.sh", dest: "/root/scripts/export-privs.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/mysqltuner.pl", dest: "/root/scripts/mysqltuner.pl", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/promote-master.sh", dest: "/root/scripts/promote-master.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/promote-slave.sh", dest: "/root/scripts/promote-slave.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/README.md", dest: "/root/scripts/README.md", mode: "u=rw,g=r,o=r" } + - { src: "files/scripts/scheduler-log.sh", dest: "/root/scripts/scheduler-log.sh", mode: "u=rwx,g=rx,o=rx" } + - { src: "files/scripts/sync-conf.sh", dest: "/root/scripts/sync-conf.sh", mode: "u=rwx,g=rx,o=rx" } diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 514f8b4..1f18ef7 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,6 +1,3 @@ -# Percona things pmm2-client https://docs.percona.com/percona-monitoring-and-management/setting-up/client/index.html#package-manager -# Add backup directory custom scripts - - name: Ensure Install requirements for MariaDB repository setup script apt: name: "{{ mariadb_requeriments }}" @@ -11,13 +8,25 @@ get_url: url: "https://r.mariadb.com/downloads/mariadb_repo_setup" dest: "/tmp/mariadb_repo_setup" - mode: "u=rwx,g=rx,o=rx" + mode: "u=rwx,g=rx,o=rx" - name: Run MariaDB repository setup script command: cmd: "/bin/bash /tmp/mariadb_repo_setup --mariadb-server-version=10.11.10" creates: "/etc/apt/sources.list.d/mariadb.list" +- name: Download Percona repository package + get_url: + url: "https://repo.percona.com/apt/percona-release_latest.generic_all.deb" + dest: "/tmp/percona-release_latest.generic_all.deb" + mode: "u=rw,g=r,o=r" + +- name: Install Percona repository package + apt: + deb: "/tmp/percona-release_latest.generic_all.deb" + state: present + install_recommends: no + - name: Install MariaDB packages apt: name: "{{ mariadb_base_packages }}" @@ -31,48 +40,29 @@ owner: "{{ item.owner }}" group: "{{ item.group }}" mode: "{{ item.mode }}" - loop: - - { path: /mnt/local-backup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - - { path: /mnt/mysqltmp, owner: root, group: root, mode: 'u=rwx,g=rwx,o=rwxt' } - - { path: /mnt/mysqlbin/binlog, owner: mysql, group: mysql, mode: 'u=rwx,g=,o=' } - - { path: /var/log/mysql, owner: mysql, group: adm, mode: 'u=rwx,g=rxs,o=' } - - { path: /root/scripts, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx'} - - { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } + loop: "{{ required_directories }}" -- name: Ensure required files are copied to their destinations +- name: Ensure required custom and Mariabackup files are copied to their destinations and root scripts copy: src: "{{ item.src }}" dest: "{{ item.dest }}" owner: root group: root mode: "{{ item.mode }}" - loop: - - { src: 'files/scripts/README.md', dest: '/root/scripts/README.md', mode: 'u=rw,g=r,o=r' } - - { src: 'mariadb_override.conf', dest: '/etc/systemd/system/mariadb.service.d/override.conf', mode: 'u=rw,g=r,o=r' } - - { src: 'files/scripts/mysqltuner.pl', dest: '/root/scripts/mysqltuner.pl', mode: 'u=rwx,g=rx,o=rx' } + loop: "{{ required_files_and_mariabackup_files_and_root_scripts }}" notify: reload systemd -- name: Set MariaDB custom root scripts - copy: - src: "{{ item }}" - dest: /root/scripts/ - owner: root - group: root - mode: u=rwx,g=rx,o=rx - with_fileglob: - - "files/scripts/*.sh" - - name: Add tmpfs in /etc/fstab blockinfile: path: /etc/fstab marker: "# {mark} ANSIBLE-MANAGED TMPFS ENTRY" block: | tmpfs /mnt/mysqltmp tmpfs rw,size=6144M 0 0 + register: fstab - name: Mount all filesystems from /etc/fstab command: mount -a - #when: ansible_facts.mounts | selectattr('mount', 'equalto', '/mnt/mysqltmp') | list | length == 0 + when: fstab.changed - name: Set MariaDB Cron to /etc/cron.d template: From 5f4a7c4cd6762ca21b8d58be7d13ef59904c2356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 14:05:05 +0100 Subject: [PATCH 21/41] Refs #8140: MariaDB Server Deploy - Role WIP - Finishing tasks - Study mysqltuner.pl and check-memory --- roles/services/files/scripts/check-memory.sh | 2 +- roles/services/tasks/mariadb.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/roles/services/files/scripts/check-memory.sh b/roles/services/files/scripts/check-memory.sh index cfb5938..d04ed75 100755 --- a/roles/services/files/scripts/check-memory.sh +++ b/roles/services/files/scripts/check-memory.sh @@ -1,6 +1,6 @@ #!/bin/bash -minFree=5 +minFree=1 memFree=$(free --gibi | awk '$1 == "Mem:" { print $7 }') if [ "$memFree" -le "$minFree" ]; then diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 1f18ef7..9472247 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,3 +1,6 @@ +# Revisar /root/scripts/check-memory.sh --> No es óptimo hacer lo que hace ese programa +# Ver como lanzar el mysqltuner.pl + - name: Ensure Install requirements for MariaDB repository setup script apt: name: "{{ mariadb_requeriments }}" From 7814245b992c4a9a64f9cb90425e9890190ee7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 14:28:55 +0100 Subject: [PATCH 22/41] Refs #8140: MariaDB Server Deploy - Role WIP --- roles/services/defaults/main.yaml | 3 +++ roles/services/tasks/mariadb.yml | 15 +++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index 646f9e9..d33e66b 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -27,3 +27,6 @@ required_files_and_mariabackup_files_and_root_scripts: - { src: "files/scripts/README.md", dest: "/root/scripts/README.md", mode: "u=rw,g=r,o=r" } - { src: "files/scripts/scheduler-log.sh", dest: "/root/scripts/scheduler-log.sh", mode: "u=rwx,g=rx,o=rx" } - { src: "files/scripts/sync-conf.sh", dest: "/root/scripts/sync-conf.sh", mode: "u=rwx,g=rx,o=rx" } +downloads: + - { url: "https://r.mariadb.com/downloads/mariadb_repo_setup", dest: "/tmp/mariadb_repo_setup", mode: "u=rwx,g=rx,o=rx" } + - { url: "https://repo.percona.com/apt/percona-release_latest.generic_all.deb", dest: "/tmp/percona-release_latest.generic_all.deb", mode: "u=rw,g=r,o=r" } diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 9472247..0e42464 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -7,23 +7,18 @@ state: present install_recommends: no -- name: Download MariaDB repository setup script +- name: Download required setup files get_url: - url: "https://r.mariadb.com/downloads/mariadb_repo_setup" - dest: "/tmp/mariadb_repo_setup" - mode: "u=rwx,g=rx,o=rx" + url: "{{ item.url }}" + dest: "{{ item.dest }}" + mode: "{{ item.mode }}" + loop: "{{ downloads }}" - name: Run MariaDB repository setup script command: cmd: "/bin/bash /tmp/mariadb_repo_setup --mariadb-server-version=10.11.10" creates: "/etc/apt/sources.list.d/mariadb.list" -- name: Download Percona repository package - get_url: - url: "https://repo.percona.com/apt/percona-release_latest.generic_all.deb" - dest: "/tmp/percona-release_latest.generic_all.deb" - mode: "u=rw,g=r,o=r" - - name: Install Percona repository package apt: deb: "/tmp/percona-release_latest.generic_all.deb" From 95b72a92c4d643d369dfbbc57de05bb570a4f402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 14:53:35 +0100 Subject: [PATCH 23/41] Refs #8140: MariaDB Server Deploy - Role WIP - Duplicate LimitNOFILE property --- roles/services/files/mariadb_override.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/roles/services/files/mariadb_override.conf b/roles/services/files/mariadb_override.conf index 215c342..678e16a 100644 --- a/roles/services/files/mariadb_override.conf +++ b/roles/services/files/mariadb_override.conf @@ -1,4 +1,3 @@ [Service] LimitNOFILE=600000 LimitMEMLOCK=2M -LimitNOFILE=81035 \ No newline at end of file From 71022c01e45f63d091d008391e28f7a978c78794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 15:09:19 +0100 Subject: [PATCH 24/41] Refs #8140: MariaDB Server Deploy - Role WIP - /root/scripts/scheduler-log.sh revisar --- roles/services/tasks/mariadb.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 0e42464..eb923e7 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,5 +1,6 @@ # Revisar /root/scripts/check-memory.sh --> No es óptimo hacer lo que hace ese programa # Ver como lanzar el mysqltuner.pl +# Revisar la tarea del cron tambien /root/scripts/scheduler-log.sh - name: Ensure Install requirements for MariaDB repository setup script apt: From f66338f4e1de0fa2b39110aee0f8a284f46330d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Tue, 17 Dec 2024 15:40:21 +0100 Subject: [PATCH 25/41] Refs #8140: MariaDB Server Deploy - Role WIP - High Performance MySQL Tuning Script update to 2.6.1 --- roles/services/files/scripts/mysqltuner.pl | 1932 +++++++++++++------- 1 file changed, 1267 insertions(+), 665 deletions(-) diff --git a/roles/services/files/scripts/mysqltuner.pl b/roles/services/files/scripts/mysqltuner.pl index 2b526d2..ebbfcbb 100755 --- a/roles/services/files/scripts/mysqltuner.pl +++ b/roles/services/files/scripts/mysqltuner.pl @@ -1,8 +1,8 @@ #!/usr/bin/env perl -# mysqltuner.pl - Version 1.9.9 +# mysqltuner.pl - Version 2.6.1 # High Performance MySQL Tuning Script -# Copyright (C) 2006-2022 Major Hayden - major@mhtx.net -# Copyright (C) 2006-2022 Jean-Marie Renouard - jmrenouard@gmail.com +# Copyright (C) 2015-2023 Jean-Marie Renouard - jmrenouard@gmail.com +# Copyright (C) 2006-2023 Major Hayden - major@mhtx.net # For the latest updates, please visit http://mysqltuner.pl/ # Git repository available at https://github.com/major/MySQLTuner-perl @@ -32,7 +32,7 @@ # Simon Greenaway Adam Stein Isart Montane # Baptiste M. Cole Turner Major Hayden # Joe Ashcraft Jean-Marie Renouard Christian Loos -# Julien Francoz Daniel Black +# Julien Francoz Daniel Black Long Radix # # Inspired by Matthew Montgomery's tuning-primer.sh script: # http://www.day32.com/MySQL/ @@ -57,89 +57,105 @@ use Cwd 'abs_path'; #use Env; # Set up a few variables for use in the script -my $tunerversion = "1.9.9"; +my $tunerversion = "2.6.1"; my ( @adjvars, @generalrec ); # Set defaults my %opt = ( - "silent" => 0, - "nobad" => 0, - "nogood" => 0, - "noinfo" => 0, - "debug" => 0, - "nocolor" => ( !-t STDOUT ), - "color" => 0, - "forcemem" => 0, - "forceswap" => 0, - "host" => 0, - "socket" => 0, - "port" => 0, - "user" => 0, - "pass" => 0, - "password" => 0, - "ssl-ca" => 0, - "skipsize" => 0, - "checkversion" => 0, - "updateversion" => 0, - "buffers" => 0, - "passwordfile" => 0, - "bannedports" => '', - "maxportallowed" => 0, - "outputfile" => 0, - "noprocess" => 0, - "dbstat" => 0, - "nodbstat" => 0, - "server-log" => '', - "tbstat" => 0, - "notbstat" => 0, - "colstat" => 0, - "nocolstat" => 0, - "idxstat" => 0, - "noidxstat" => 0, - "sysstat" => 0, - "nosysstat" => 0, - "pfstat" => 0, - "nopfstat" => 0, - "skippassword" => 0, - "noask" => 0, - "template" => 0, - "json" => 0, - "prettyjson" => 0, - "reportfile" => 0, - "verbose" => 0, - "defaults-file" => '', - "protocol" => '', + "silent" => 0, + "nobad" => 0, + "nogood" => 0, + "noinfo" => 0, + "debug" => 0, + "nocolor" => ( !-t STDOUT ), + "color" => ( -t STDOUT ), + "forcemem" => 0, + "forceswap" => 0, + "host" => 0, + "socket" => 0, + "port" => 0, + "user" => 0, + "pass" => 0, + "password" => 0, + "ssl-ca" => 0, + "skipsize" => 0, + "checkversion" => 0, + "updateversion" => 0, + "buffers" => 0, + "passwordfile" => 0, + "bannedports" => '', + "maxportallowed" => 0, + "outputfile" => 0, + "noprocess" => 0, + "dbstat" => 0, + "nodbstat" => 0, + "server-log" => '', + "tbstat" => 0, + "notbstat" => 0, + "colstat" => 0, + "nocolstat" => 0, + "idxstat" => 0, + "noidxstat" => 0, + "nomyisamstat" => 0, + "nostructstat" => 0, + "sysstat" => 0, + "nosysstat" => 0, + "pfstat" => 0, + "nopfstat" => 0, + "skippassword" => 0, + "noask" => 0, + "template" => 0, + "json" => 0, + "prettyjson" => 0, + "reportfile" => 0, + "verbose" => 0, + "experimental" => 0, + "nondedicated" => 0, + "defaults-file" => '', + "defaults-extra-file" => '', + "protocol" => '', + "dumpdir" => '', + "feature" => '', + "dbgpattern" => '', + "defaultarch" => 64, + "noprettyicon" => 0 ); # Gather the options from the command line GetOptions( - \%opt, 'nobad', - 'nogood', 'noinfo', - 'debug', 'nocolor', - 'forcemem=i', 'forceswap=i', - 'host=s', 'socket=s', - 'port=i', 'user=s', - 'pass=s', 'skipsize', - 'checkversion', 'mysqladmin=s', - 'mysqlcmd=s', 'help', - 'buffers', 'skippassword', - 'passwordfile=s', 'outputfile=s', - 'silent', 'noask', - 'json', 'prettyjson', - 'template=s', 'reportfile=s', - 'cvefile=s', 'bannedports=s', - 'updateversion', 'maxportallowed=s', - 'verbose', 'password=s', - 'passenv=s', 'userenv=s', - 'defaults-file=s', 'ssl-ca=s', - 'color', 'noprocess', - 'dbstat', 'nodbstat', - 'tbstat', 'notbstat', - 'colstat', 'nocolstat', - 'sysstat', 'nosysstat', - 'pfstat', 'nopfstat', - 'idxstat', 'noidxstat', - 'server-log=s', 'protocol=s', + \%opt, 'nobad', + 'nogood', 'noinfo', + 'debug', 'nocolor', + 'forcemem=i', 'forceswap=i', + 'host=s', 'socket=s', + 'port=i', 'user=s', + 'pass=s', 'skipsize', + 'checkversion', 'mysqladmin=s', + 'mysqlcmd=s', 'help', + 'buffers', 'skippassword', + 'passwordfile=s', 'outputfile=s', + 'silent', 'noask', + 'json', 'prettyjson', + 'template=s', 'reportfile=s', + 'cvefile=s', 'bannedports=s', + 'updateversion', 'maxportallowed=s', + 'verbose', 'password=s', + 'passenv=s', 'userenv=s', + 'defaults-file=s', 'ssl-ca=s', + 'color', 'noprocess', + 'dbstat', 'nodbstat', + 'tbstat', 'notbstat', + 'colstat', 'nocolstat', + 'sysstat', 'nosysstat', + 'pfstat', 'nopfstat', + 'idxstat', 'noidxstat', + 'structstat', 'nostructstat', + 'myisamstat', 'nomyisamstat', + 'server-log=s', 'protocol=s', + 'defaults-extra-file=s', 'dumpdir=s', + 'feature=s', 'dbgpattern=s', + 'defaultarch=i', 'experimental', + 'nondedicated', 'noprettyicon' ) or pod2usage( -exitval => 1, @@ -184,31 +200,57 @@ if ( exists $opt{passenv} && exists $ENV{ $opt{passenv} } ) { } $opt{pass} = $opt{password} if ( $opt{pass} eq 0 and $opt{password} ne 0 ); +if ( $opt{dumpdir} ne '' ) { + $opt{dumpdir} = abs_path( $opt{dumpdir} ); + if ( !-d $opt{dumpdir} ) { + mkdir $opt{dumpdir} or die "Cannot create directory $opt{dumpdir}: $!"; + } +} + # for RPM distributions $basic_password_files = "/usr/share/mysqltuner/basic_passwords.txt" unless -f "$basic_password_files"; +$opt{dbgpattern} = '.*' if ( $opt{dbgpattern} eq '' ); + +# Activate debug variables +#if ( $opt{debug} ne '' ) { $opt{debug} = 2; } +# Activate experimental calculations and analysis +#if ( $opt{experimental} ne '' ) { $opt{experimental} = 1; } + # check if we need to enable verbose mode +if ( $opt{feature} ne '' ) { $opt{verbose} = 1; } if ( $opt{verbose} ) { - $opt{checkversion} = 1; # Check for updates to MySQLTuner + $opt{checkversion} = 0; # Check for updates to MySQLTuner $opt{dbstat} = 1; # Print database information $opt{tbstat} = 1; # Print database information $opt{idxstat} = 1; # Print index information $opt{sysstat} = 1; # Print index information $opt{buffers} = 1; # Print global and per-thread buffer values $opt{pfstat} = 1; # Print performance schema info. + $opt{structstat} = 1; # Print table structure information + $opt{myisamstat} = 1; # Print MyISAM table information + $opt{cvefile} = 'vulnerabilities.csv'; #CVE File for vulnerability checks } +$opt{noprettyicon}=0 if $opt{noprettyicon}!=1; $opt{nocolor} = 1 if defined( $opt{outputfile} ); -$opt{tbstat} = 0 if ( $opt{notbstat} == 1 ); # Don't Print table information -$opt{colstat} = 0 if ( $opt{nocolstat} == 1 ); # Don't Print column information -$opt{dbstat} = 0 if ( $opt{nodbstat} == 1 ); # Don't Print database information +$opt{tbstat} = 0 if ( $opt{notbstat} == 1 ); # Don't print table information +$opt{colstat} = 0 if ( $opt{nocolstat} == 1 ); # Don't print column information +$opt{dbstat} = 0 if ( $opt{nodbstat} == 1 ); # Don't print database information $opt{noprocess} = 0 - if ( $opt{noprocess} == 1 ); # Don't Print process information -$opt{sysstat} = 0 if ( $opt{nosysstat} == 1 ); # Don't Print sysstat information + if ( $opt{noprocess} == 1 ); # Don't print process information +$opt{sysstat} = 0 if ( $opt{nosysstat} == 1 ); # Don't print sysstat information $opt{pfstat} = 0 - if ( $opt{nopfstat} == 1 ); # Don't Print performance schema information -$opt{idxstat} = 0 if ( $opt{noidxstat} == 1 ); # Don't Print index information + if ( $opt{nopfstat} == 1 ); # Don't print performance schema information +$opt{idxstat} = 0 if ( $opt{noidxstat} == 1 ); # Don't print index information +$opt{structstat} = 0 + if ( not defined( $opt{structstat} ) or $opt{nostructstat} == 1 ) + ; # Don't print table struct information +$opt{myisamstat} = 1 + if ( not defined( $opt{myisamstat} ) ); +$opt{myisamstat} = 0 + if ( $opt{nomyisamstat} == 1 ); # Don't print MyISAM table information # for RPM distributions $opt{cvefile} = "/usr/share/mysqltuner/vulnerabilities.csv" @@ -236,7 +278,7 @@ $opt{nocolor} = 0 if ( $opt{color} == 1 ); my $me = `whoami`; $me =~ s/\n//g; -# Setting up the colors for the print styles + my $good = ( $opt{nocolor} == 0 ) ? "[\e[0;32mOK\e[0m]" : "[OK]"; my $bad = ( $opt{nocolor} == 0 ) ? "[\e[0;31m!!\e[0m]" : "[!!]"; my $info = ( $opt{nocolor} == 0 ) ? "[\e[0;34m--\e[0m]" : "[--]"; @@ -244,6 +286,15 @@ my $deb = ( $opt{nocolor} == 0 ) ? "[\e[0;31mDG\e[0m]" : "[DG]"; my $cmd = ( $opt{nocolor} == 0 ) ? "\e[1;32m[CMD]($me)" : "[CMD]($me)"; my $end = ( $opt{nocolor} == 0 ) ? "\e[0m" : ""; +if ($opt{noprettyicon} == 0) { + $good = "✔ "; + $bad = "✘ "; + $info = "ℹ "; + $deb = "⚙ "; + $cmd = "⌨️($me)"; + $end = " "; +} + # Maximum lines of log output to read from end my $maxlines = 30000; @@ -255,19 +306,31 @@ my @dblist; # Super structure containing all information my %result; -$result{'MySQLTuner'}{'version'} = $tunerversion; -$result{'MySQLTuner'}{'datetime'} =`date '+%d-%m-%Y %H:%M:%S'`; -$result{'MySQLTuner'}{'options'} = \%opt; +$result{'MySQLTuner'}{'version'} = $tunerversion; +$result{'MySQLTuner'}{'datetime'} = `date '+%d-%m-%Y %H:%M:%S'`; +$result{'MySQLTuner'}{'options'} = \%opt; # Functions that handle the print styles sub prettyprint { print $_[0] . "\n" unless ( $opt{'silent'} or $opt{'json'} ); print $fh $_[0] . "\n" if defined($fh); } -sub goodprint { prettyprint $good. " " . $_[0] unless ( $opt{nogood} == 1 ); } -sub infoprint { prettyprint $info. " " . $_[0] unless ( $opt{noinfo} == 1 ); } -sub badprint { prettyprint $bad. " " . $_[0] unless ( $opt{nobad} == 1 ); } -sub debugprint { prettyprint $deb. " " . $_[0] unless ( $opt{debug} == 0 ); } + +sub goodprint { + prettyprint $good. " " . $_[0] unless ( $opt{nogood} == 1 ); +} + +sub infoprint { + prettyprint $info. " " . $_[0] unless ( $opt{noinfo} == 1 ); +} + +sub badprint { + prettyprint $bad. " " . $_[0] unless ( $opt{nobad} == 1 ); +} + +sub debugprint { + prettyprint $deb. " " . $_[0] unless ( $opt{debug} == 0 ); +} sub redwrap { return ( $opt{nocolor} == 0 ) ? "\e[0;31m" . $_[0] . "\e[0m" : $_[0]; @@ -276,7 +339,10 @@ sub redwrap { sub greenwrap { return ( $opt{nocolor} == 0 ) ? "\e[0;32m" . $_[0] . "\e[0m" : $_[0]; } -sub cmdprint { prettyprint $cmd. " " . $_[0] . $end; } + +sub cmdprint { + prettyprint $cmd. " " . $_[0] . $end; +} sub infoprintml { for my $ln (@_) { $ln =~ s/\n//g; infoprint "\t$ln"; } @@ -301,6 +367,31 @@ sub infoprinthcmd { infoprintcmd "$_[1]"; } +sub is_remote() { + my $host = $opt{'host'}; + return 0 if ( $host eq '' ); + return 0 if ( $host eq 'localhost' ); + return 0 if ( $host eq '127.0.0.1' ); + return 1; +} + +sub is_int { + return 0 unless defined $_[0]; + my $str = $_[0]; + + #trim whitespace both sides + $str =~ s/^\s+|\s+$//g; + + #Alternatively, to match any float-like numeric, use: + # m/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ + + #flatten to string and match dash or plus and one or more digits + if ( $str =~ /^(\-|\+)?\d+?$/ ) { + return 1; + } + return 0; +} + # Calculates the number of physical cores considering HyperThreading sub cpu_cores { if ( $^O eq 'linux' ) { @@ -323,14 +414,15 @@ sub hr_bytes { my $num = shift; return "0B" unless defined($num); return "0B" if $num eq "NULL"; + return "0B" if $num eq ""; - if ( $num >= ( 1024**3 ) ) { #GB + if ( $num >= ( 1024**3 ) ) { # GB return sprintf( "%.1f", ( $num / ( 1024**3 ) ) ) . "G"; } - elsif ( $num >= ( 1024**2 ) ) { #MB + elsif ( $num >= ( 1024**2 ) ) { # MB return sprintf( "%.1f", ( $num / ( 1024**2 ) ) ) . "M"; } - elsif ( $num >= 1024 ) { #KB + elsif ( $num >= 1024 ) { # KB return sprintf( "%.1f", ( $num / 1024 ) ) . "K"; } else { @@ -363,13 +455,13 @@ sub hr_bytes_rnd { return "0B" unless defined($num); return "0B" if $num eq "NULL"; - if ( $num >= ( 1024**3 ) ) { #GB + if ( $num >= ( 1024**3 ) ) { # GB return int( ( $num / ( 1024**3 ) ) ) . "G"; } - elsif ( $num >= ( 1024**2 ) ) { #MB + elsif ( $num >= ( 1024**2 ) ) { # MB return int( ( $num / ( 1024**2 ) ) ) . "M"; } - elsif ( $num >= 1024 ) { #KB + elsif ( $num >= 1024 ) { # KB return int( ( $num / 1024 ) ) . "K"; } else { @@ -404,7 +496,7 @@ sub percentage { return sprintf( "%.2f", ( $value * 100 / $total ) ); } -# Calculates uptime to display in a more attractive form +# Calculates uptime to display in a human-readable form sub pretty_uptime { my $uptime = shift; my $seconds = $uptime % 60; @@ -516,6 +608,8 @@ sub os_setup { chomp($physical_memory); chomp($swap_memory); chomp($os); + $physical_memory = $opt{forcemem} + if ( defined( $opt{forcemem} ) and $opt{forcemem} gt 0 ); $result{'OS'}{'OS Type'} = $os; $result{'OS'}{'Physical Memory'}{'bytes'} = $physical_memory; $result{'OS'}{'Physical Memory'}{'pretty'} = hr_bytes($physical_memory); @@ -581,7 +675,7 @@ sub validate_tuner_version { debugprint "curl and wget are not available."; infoprint "Unable to check for the latest MySQLTuner version"; infoprint -"Using --pass and --password option is insecure during MySQLTuner execution(Password disclosure)" +"Using --pass and --password option is insecure during MySQLTuner execution (password disclosure)" if ( defined( $opt{'pass'} ) ); } @@ -594,7 +688,7 @@ sub update_tuner_version { } my $update; - my $fullpath=""; + my $fullpath = ""; my $url = "https://raw.githubusercontent.com/major/MySQLTuner-perl/master/"; my @scripts = ( "mysqltuner.pl", "basic_passwords.txt", "vulnerabilities.csv" ); @@ -607,12 +701,12 @@ sub update_tuner_version { if ( $httpcli =~ /curl$/ ) { debugprint "$httpcli is available."; - $fullpath=dirname(__FILE__)."/".$script; + $fullpath = dirname(__FILE__) . "/" . $script; debugprint "FullPath: $fullpath"; debugprint - "$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath"; +"$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath"; $update = - `$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath`; +`$httpcli --connect-timeout 3 '$url$script' 2>$devnull > $fullpath`; chomp($update); debugprint "$script updated: $update"; @@ -655,7 +749,7 @@ sub update_tuner_version { else { badprint "Couldn't update MySQLTuner script"; } - infoprint "Stopping program: MySQLTuner has be updated."; + infoprint "Stopping program: MySQLTuner script must be updated first."; exit 0; } @@ -666,11 +760,11 @@ sub compare_tuner_version { #exit 0; if ( $remoteversion ne $tunerversion ) { badprint - "There is a new version of MySQLTuner available($remoteversion)"; + "There is a new version of MySQLTuner available ($remoteversion)"; update_tuner_version(); return; } - goodprint "You have the latest version of MySQLTuner($tunerversion)"; + goodprint "You have the latest version of MySQLTuner ($tunerversion)"; return; } @@ -681,7 +775,7 @@ my $osname = $^O; if ( $osname eq 'MSWin32' ) { eval { require Win32; } or last; $osname = Win32::GetOSName(); - infoprint "* Windows OS($osname) is not fully supported.\n"; + infoprint "* Windows OS ($osname) is not fully supported.\n"; #exit 1; } @@ -693,9 +787,9 @@ sub mysql_setup { $mysqladmincmd = $opt{mysqladmin}; } else { - $mysqladmincmd = which( "mysqladmin", $ENV{'PATH'} ); + $mysqladmincmd = which( "mariadb-admin", $ENV{'PATH'} ); if ( !-e $mysqladmincmd ) { - $mysqladmincmd = which( "mariadb-admin", $ENV{'PATH'} ); + $mysqladmincmd = which( "mysqladmin", $ENV{'PATH'} ); } } chomp($mysqladmincmd); @@ -707,15 +801,16 @@ sub mysql_setup { elsif ( !-e $mysqladmincmd ) { badprint "Couldn't find mysqladmin/mariadb-admin in your \$PATH. Is MySQL installed?"; - exit 1; + + #exit 1; } if ( $opt{mysqlcmd} ) { $mysqlcmd = $opt{mysqlcmd}; } else { - $mysqlcmd = which( "mysql", $ENV{'PATH'} ); + $mysqlcmd = which( "mariadb", $ENV{'PATH'} ); if ( !-e $mysqlcmd ) { - $mysqlcmd = which( "mariadb", $ENV{'PATH'} ); + $mysqlcmd = which( "mysql", $ENV{'PATH'} ); } } chomp($mysqlcmd); @@ -740,34 +835,47 @@ sub mysql_setup { debugprint "MySQL Client: $mysqlcmd"; - $opt{port} = ( $opt{port} eq 0 ) ? 3306 : $opt{port}; - # Are we being asked to connect via a socket? if ( $opt{socket} ne 0 ) { - $remotestring = " -S $opt{socket} -P $opt{port}"; + if ( $opt{port} ne 0 ) { + $remotestring = " -S $opt{socket} -P $opt{port}"; + } + else { + $remotestring = " -S $opt{socket}"; + } } - if ( $opt{protocol} ne '' ){ + if ( $opt{protocol} ne '' ) { $remotestring = " --protocol=$opt{protocol}"; } # Are we being asked to connect to a remote server? if ( $opt{host} ne 0 ) { chomp( $opt{host} ); + $opt{port} = ( $opt{port} eq 0 ) ? 3306 : $opt{port}; # If we're doing a remote connection, but forcemem wasn't specified, we need to exit - if ( $opt{'forcemem'} eq 0 - && ( $opt{host} ne "127.0.0.1" ) - && ( $opt{host} ne "localhost" ) ) - { + if ( $opt{'forcemem'} eq 0 && is_remote eq 1 ) { badprint "The --forcemem option is required for remote connections"; - exit 1; + badprint + "Assuming RAM memory is 1Gb for simplify remote connection usage"; + $opt{'forcemem'} = 1024; + + #exit 1; + } + if ( $opt{'forceswap'} eq 0 && is_remote eq 1 ) { + badprint + "The --forceswap option is required for remote connections"; + badprint + "Assuming Swap size is 1Gb for simplify remote connection usage"; + $opt{'forceswap'} = 1024; + + #exit 1; } infoprint "Performing tests on $opt{host}:$opt{port}"; $remotestring = " -h $opt{host} -P $opt{port}"; - if ( ( $opt{host} ne "127.0.0.1" ) && ( $opt{host} ne "localhost" ) ) { - $doremote = 1; - } + $doremote = is_remote(); + } else { $opt{host} = '127.0.0.1'; @@ -787,10 +895,14 @@ sub mysql_setup { } } - # Did we already get a username without password on the command line? - if ( $opt{user} ne 0 and $opt{pass} eq 0 ) { - $mysqllogin = "-u $opt{user} " . $remotestring; - my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; + # Did we already get a username with or without password on the command line? + if ( $opt{user} ne 0 ) { + $mysqllogin = + "-u $opt{user} " + . ( ( $opt{pass} ne 0 ) ? "-p'$opt{pass}' " : " " ) + . $remotestring; + my $loginstatus = + `$mysqlcmd -Nrs -e 'select "mysqld is alive";' $mysqllogin 2>&1`; if ( $loginstatus =~ /mysqld is alive/ ) { goodprint "Logged in using credentials passed on the command line"; return 1; @@ -802,20 +914,6 @@ sub mysql_setup { } } - # Did we already get a username and password passed on the command line? - if ( $opt{user} ne 0 and $opt{pass} ne 0 ) { - $mysqllogin = "-u $opt{user} -p'$opt{pass}'" . $remotestring; - my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; - if ( $loginstatus =~ /mysqld is alive/ ) { - goodprint "Logged in using credentials passed on the command line"; - return 1; - } - else { - badprint - "Attempted to use login credentials, but they were invalid"; - exit 1; - } - } my $svcprop = which( "svcprop", $ENV{'PATH'} ); if ( substr( $svcprop, 0, 1 ) =~ "/" ) { @@ -915,10 +1013,41 @@ sub mysql_setup { return 1; } } + elsif ( $opt{'defaults-extra-file'} ne '' + and -r "$opt{'defaults-extra-file'}" ) + { + + # defaults-extra-file + debugprint "defaults extra file detected: $opt{'defaults-extra-file'}"; + my $mysqlclidefaults = `$mysqlcmd --print-defaults`; + debugprint + "MySQL Client Extra Default File: $opt{'defaults-extra-file'}"; + + $mysqllogin = "--defaults-extra-file=" . $opt{'defaults-extra-file'}; + my $loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1`; + if ( $loginstatus =~ /mysqld is alive/ ) { + goodprint + "Logged in using credentials from extra defaults file account."; + return 1; + } + } else { # It's not Plesk or Debian, we should try a login debugprint "$mysqladmincmd $remotestring ping 2>&1"; - my $loginstatus = `$mysqladmincmd $remotestring ping 2>&1`; + + #my $loginstatus = ""; + debugprint "Using mysqlcmd: $mysqlcmd"; + + #if (defined($mysqladmincmd)) { + # infoprint "Using mysqladmin to check login"; + # $loginstatus=`$mysqladmincmd $remotestring ping 2>&1`; + #} else { + infoprint "Using mysql to check login"; + my $loginstatus = +`$mysqlcmd $remotestring -Nrs -e 'select "mysqld is alive"' --connect-timeout=3 2>&1`; + + #} + if ( $loginstatus =~ /mysqld is alive/ ) { # Login went just fine @@ -932,7 +1061,7 @@ sub mysql_setup { unless ( -e "${userpath}/.my.cnf" or -e "${userpath}/.mylogin.cnf" ) { badprint -"Successfully authenticated with no password - SECURITY RISK!"; + "SECURITY RISK: Successfully authenticated without password"; } return 1; } @@ -974,20 +1103,23 @@ sub mysql_setup { $mysqllogin .= $remotestring; my $loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1`; if ( $loginstatus =~ /mysqld is alive/ ) { - print STDERR ""; + + #print STDERR ""; if ( !length($password) ) { # Did this go well because of a .my.cnf file or is there no password set? my $userpath = `printenv HOME`; chomp($userpath); unless ( -e "$userpath/.my.cnf" ) { + print STDERR ""; badprint -"Successfully authenticated with no password - SECURITY RISK!"; +"SECURITY RISK: Successfully authenticated without password"; } } return 1; } else { + #print STDERR ""; badprint "Attempted to use login credentials, but they were invalid."; exit 1; @@ -1016,10 +1148,49 @@ sub select_array { return @result; } +# MySQL Request Array +sub select_array_with_headers { + my $req = shift; + debugprint "PERFORM: $req "; + my @result = `$mysqlcmd $mysqllogin -Bre "\\w$req" 2>>/dev/null`; + if ( $? != 0 ) { + badprint "Failed to execute: $req"; + badprint "FAIL Execute SQL / return code: $?"; + debugprint "CMD : $mysqlcmd"; + debugprint "OPTIONS: $mysqllogin"; + debugprint `$mysqlcmd $mysqllogin -Bse "$req" 2>&1`; + + #exit $?; + } + debugprint "select_array_with_headers: return code : $?"; + chomp(@result); + return @result; +} + +# MySQL Request Array +sub select_csv_file { + my $tfile = shift; + my $req = shift; + debugprint "PERFORM: $req CSV into $tfile"; + + #return; + my @result = select_array_with_headers($req); + open( my $fh, '>', $tfile ) or die "Could not open file '$tfile' $!"; + for my $l (@result) { + $l =~ s/\t/","/g; + $l =~ s/^/"/; + $l =~ s/$/"\n/; + print $fh $l; + print $l if $opt{debug}; + } + close $fh; + infoprint "CSV file $tfile created"; +} + sub human_size { my ( $size, $n ) = ( shift, 0 ); ++$n and $size /= 1024 until $size < 1024; - return sprintf "%.2f %s", $size, (qw[ bytes KB MB GB ])[$n]; + return sprintf "%.2f %s", $size, (qw[ bytes KB MB GB TB ])[$n]; } # MySQL Request one @@ -1079,14 +1250,14 @@ sub select_user_dbs { ); } -sub select_tables_db() { +sub select_tables_db { my $schema = shift; return select_array( "SELECT DISTINCT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$schema'" ); } -sub select_indexes_db() { +sub select_indexes_db { my $schema = shift; return select_array( "SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema'" @@ -1159,12 +1330,18 @@ sub arr2hash { my $href = shift; my $harr = shift; my $sep = shift; + my $key = ''; + my $val = ''; + $sep = '\s' unless defined($sep); foreach my $line (@$harr) { next if ( $line =~ m/^\*\*\*\*\*\*\*/ ); $line =~ /([a-zA-Z_]*)\s*$sep\s*(.*)/; - $$href{$1} = $2; - debugprint "V: $1 = $2"; + $key = $1; + $val = $2; + $$href{$key} = $val; + + debugprint " * $key = $val" if $key =~ /$opt{dbgpattern}/i; } } @@ -1174,7 +1351,7 @@ sub get_all_vars { $dummyselect = select_one "SELECT VERSION()"; if ( not defined($dummyselect) or $dummyselect eq "" ) { badprint -"You probably did not get enough privileges for running MySQLTuner ..."; + "You probably do not have enough privileges to run MySQLTuner ..."; exit(256); } $dummyselect =~ s/(.*?)\-.*/$1/; @@ -1272,8 +1449,8 @@ sub get_all_vars { my @lineitems = (); foreach my $line (@mysqlslaves) { debugprint "L: $line "; - @lineitems = split /\s+/, $line; - $myslaves{ $lineitems[0] } = $line; + @lineitems = split /\s+/, $line; + $myslaves{ $lineitems[0] } = $line; $result{'Replication'}{'Slaves'}{ $lineitems[0] } = $lineitems[4]; } } @@ -1352,6 +1529,10 @@ sub get_log_file_real_path { } sub log_file_recommendations { + if ( is_remote eq 1 ) { + infoprint "Skipping error log files checks on remote host"; + return; + } my $fh; $myvar{'log_error'} = $opt{'server-log'} || get_log_file_real_path( $myvar{'log_error'}, $myvar{'hostname'}, @@ -1359,7 +1540,8 @@ sub log_file_recommendations { subheaderprint "Log file Recommendations"; if ( "$myvar{'log_error'}" eq "stderr" ) { - badprint "log_error is set to $myvar{'log_error'} MT can't read stderr"; + badprint +"log_error is set to $myvar{'log_error'}, but this script can't read stderr"; return; } elsif ( $myvar{'log_error'} =~ /^(docker|podman|kubectl):(.*)/ ) { @@ -1382,13 +1564,13 @@ sub log_file_recommendations { if ( $size > 0 ) { goodprint "Log file $myvar{'log_error'} is not empty"; if ( $size < 32 * 1024 * 1024 ) { - goodprint "Log file $myvar{'log_error'} is smaller than 32 Mb"; + goodprint "Log file $myvar{'log_error'} is smaller than 32 MB"; } else { - badprint "Log file $myvar{'log_error'} is bigger than 32 Mb"; + badprint "Log file $myvar{'log_error'} is bigger than 32 MB"; push @generalrec, $myvar{'log_error'} - . " is > 32Mb, you should analyze why or implement a rotation log strategy such as logrotate!"; + . " is > 32MB, you should analyze why or implement a rotation log strategy such as logrotate!"; } } else { @@ -1421,10 +1603,9 @@ sub log_file_recommendations { while ( my $logLi = <$fh> ) { chomp $logLi; $numLi++; - debugprint "$numLi: $logLi" - if $logLi =~ /warning|error/i and $logLi !~ /Logging to/; - $nbErrLog++ if $logLi =~ /error/i and $logLi !~ /Logging to/; - $nbWarnLog++ if $logLi =~ /warning/i; + debugprint "$numLi: $logLi" if $logLi =~ /\[(warning|error)\]/i; + $nbErrLog++ if $logLi =~ /\[error\]/i; + $nbWarnLog++ if $logLi =~ /\[warning\]/i; push @lastShutdowns, $logLi if $logLi =~ /Shutdown complete/ and $logLi !~ /Innodb/i; push @lastStarts, $logLi if $logLi =~ /ready for connections/; @@ -1513,7 +1694,7 @@ sub cve_recommendations { if ( $mysqlvermajor eq 5 and $mysqlverminor eq 5 ) { infoprint "False positive CVE(s) for MySQL and MariaDB 5.5.x can be found."; - infoprint "Check careful each CVE for those particular versions"; + infoprint "Check carefully each CVE for those particular versions"; } badprint $cvefound . " CVE(s) found for your MySQL release."; push( @generalrec, @@ -1639,12 +1820,12 @@ sub get_fs_info { $v; } @iinfo; foreach my $info (@iinfo) { - next if $info =~ m{(\d+)\t/(run|dev|sys|proc)($|/)}; + next if $info =~ m{(\d+)\t/(run|dev|sys|proc|snap)($|/)}; if ( $info =~ /(\d+)\t(.*)/ ) { if ( $1 > 85 ) { badprint "mount point $2 is using $1 % of max allowed inodes"; push( @generalrec, -"Cleanup files from $2 mountpoint or reformat you filesystem." +"Cleanup files from $2 mountpoint or reformat your filesystem." ); } else { @@ -1712,10 +1893,10 @@ sub infocmd_one { sub get_kernel_info { my @params = ( - 'fs.aio-max-nr', 'fs.aio-nr', - 'fs.file-max', 'sunrpc.tcp_fin_timeout', - 'sunrpc.tcp_max_slot_table_entries', 'sunrpc.tcp_slot_table_entries', - 'vm.swappiness' + 'fs.aio-max-nr', 'fs.aio-nr', + 'fs.nr_open', 'fs.file-max', + 'sunrpc.tcp_fin_timeout', 'sunrpc.tcp_max_slot_table_entries', + 'sunrpc.tcp_slot_table_entries', 'vm.swappiness' ); infoprint "Information about kernel tuning:"; foreach my $param (@params) { @@ -1725,9 +1906,9 @@ sub get_kernel_info { if ( `sysctl -n vm.swappiness` > 10 ) { badprint "Swappiness is > 10, please consider having a value lower than 10"; - push @generalrec, "setup swappiness lower or equals to 10"; + push @generalrec, "setup swappiness lower or equal to 10"; push @adjvars, - 'vm.swappiness <= 10 (echo 10 > /proc/sys/vm/swappiness)'; +'vm.swappiness <= 10 (echo 10 > /proc/sys/vm/swappiness) or vm.swappiness=10 in /etc/sysctl.conf'; } else { infoprint "Swappiness is < 10."; @@ -1743,7 +1924,7 @@ sub get_kernel_info { "Initial TCP slot entries is < 1M, please consider having a value greater than 100"; push @generalrec, "setup Initial TCP slot entries greater than 100"; push @adjvars, -'sunrpc.tcp_slot_table_entries > 100 (echo 128 > /proc/sys/sunrpc/tcp_slot_table_entries)'; +'sunrpc.tcp_slot_table_entries > 100 (echo 128 > /proc/sys/sunrpc/tcp_slot_table_entries) or sunrpc.tcp_slot_table_entries=128 in /etc/sysctl.conf'; } else { infoprint "TCP slot entries is > 100."; @@ -1752,15 +1933,28 @@ sub get_kernel_info { if ( -f "/proc/sys/fs/aio-max-nr" ) { if ( `sysctl -n fs.aio-max-nr` < 1000000 ) { badprint -"Max running total of the number of events is < 1M, please consider having a value greater than 1M"; +"Max running total of the number of max. events is < 1M, please consider having a value greater than 1M"; push @generalrec, "setup Max running number events greater than 1M"; push @adjvars, - 'fs.aio-max-nr > 1M (echo 1048576 > /proc/sys/fs/aio-max-nr)'; +'fs.aio-max-nr > 1M (echo 1048576 > /proc/sys/fs/aio-max-nr) or fs.aio-max-nr=1048576 in /etc/sysctl.conf'; } else { infoprint "Max Number of AIO events is > 1M."; } } + if ( -f "/proc/sys/fs/nr_open" ) { + if ( `sysctl -n fs.nr_open` < 1000000 ) { + badprint +"Max running total of the number of file open request is < 1M, please consider having a value greater than 1M"; + push @generalrec, + "setup running number of open request greater than 1M"; + push @adjvars, +'fs.aio-nr > 1M (echo 1048576 > /proc/sys/fs/nr_open) or fs.nr_open=1048576 in /etc/sysctl.conf'; + } + else { + infoprint "Max Number of open file requests is > 1M."; + } + } } sub get_system_info { @@ -1811,15 +2005,14 @@ sub get_system_info { } infoprint "External IP : " . $ext_ip; $result{'Network'}{'External Ip'} = $ext_ip; - badprint - "External IP : Can't check because of Internet connectivity" + badprint "External IP : Can't check, no Internet connectivity" unless defined($httpcli); infoprint "Name Servers : " . infocmd_one "grep 'nameserver' /etc/resolv.conf \| awk '{print \$2}'"; infoprint "Logged In users : "; infocmd_tab "who"; $result{'OS'}{'Logged users'} = `who`; - infoprint "Ram Usages in Mb : "; + infoprint "Ram Usages in MB : "; infocmd_tab "free -m | grep -v +"; $result{'OS'}{'Free Memory RAM'} = `free -m | grep -v +`; infoprint "Load Average : "; @@ -1832,6 +2025,10 @@ sub get_system_info { } sub system_recommendations { + if ( is_remote eq 1 ) { + infoprint "Skipping system checks on remote host"; + return; + } return if ( $opt{sysstat} == 0 ); subheaderprint "System Linux Recommendations"; my $os = `uname`; @@ -1843,21 +2040,54 @@ sub system_recommendations { #prettyprint '-'x78; get_system_info(); + + my $nb_cpus = cpu_cores; + if ( $nb_cpus > 1 ) { + goodprint "There is at least one CPU dedicated to database server."; + } + else { + badprint +"There is only one CPU, consider dedicated one CPU for your database server"; + push @generalrec, + "Consider increasing number of CPU for your database server"; + } + + if ( $physical_memory >= 1.5 * 1024 ) { + goodprint "There is at least 1 Gb of RAM dedicated to Linux server."; + } + else { + badprint +"There is less than 1,5 Gb of RAM, consider dedicated 1 Gb for your Linux server"; + push @generalrec, + "Consider increasing 1,5 / 2 Gb of RAM for your Linux server"; + } + my $omem = get_other_process_memory; infoprint "User process except mysqld used " . hr_bytes_rnd($omem) . " RAM."; if ( ( 0.15 * $physical_memory ) < $omem ) { - badprint + if ( $opt{nondedicated} ) { + infoprint "No warning with --nondedicated option"; + infoprint "Other user process except mysqld used more than 15% of total physical memory " - . percentage( $omem, $physical_memory ) . "% (" - . hr_bytes_rnd($omem) . " / " - . hr_bytes_rnd($physical_memory) . ")"; - push( @generalrec, + . percentage( $omem, $physical_memory ) . "% (" + . hr_bytes_rnd($omem) . " / " + . hr_bytes_rnd($physical_memory) . ")"; + } + else { + + badprint +"Other user process except mysqld used more than 15% of total physical memory " + . percentage( $omem, $physical_memory ) . "% (" + . hr_bytes_rnd($omem) . " / " + . hr_bytes_rnd($physical_memory) . ")"; + push( @generalrec, "Consider stopping or dedicate server for additional process other than mysqld." - ); - push( @adjvars, + ); + push( @adjvars, "DON'T APPLY SETTINGS BECAUSE THERE ARE TOO MANY PROCESSES RUNNING ON THIS SERVER. OOM KILL CAN OCCUR!" - ); + ); + } } else { infoprint @@ -1873,17 +2103,17 @@ sub system_recommendations { . scalar @opened_ports . " listening port(s) on this server."; if ( scalar(@opened_ports) > $opt{'maxportallowed'} ) { - badprint "There is too many listening ports: " + badprint "There are too many listening ports: " . scalar(@opened_ports) . " opened > " . $opt{'maxportallowed'} . "allowed."; push( @generalrec, -"Consider dedicating a server for your database installation with less services running on !" +"Consider dedicating a server for your database installation with fewer services running on it!" ); } else { - goodprint "There is less than " + goodprint "There are less than " . $opt{'maxportallowed'} . " opened ports on this server."; } @@ -1893,7 +2123,7 @@ sub system_recommendations { if ( is_open_port($banport) ) { badprint "Banned port: $banport is opened.."; push( @generalrec, -"Port $banport is opened. Consider stopping program handling this port." +"Port $banport is opened. Consider stopping the program over this port." ); } else { @@ -1911,7 +2141,7 @@ sub security_recommendations { subheaderprint "Security Recommendations"; if ( mysql_version_eq(8) ) { - infoprint "Skipped due to unsupported feature for MySQL 8"; + infoprint "Skipped due to unsupported feature for MySQL 8.0+"; return; } @@ -1944,16 +2174,36 @@ sub security_recommendations { } debugprint "Password column = $PASS_COLUMN_NAME"; + # IS THERE A ROLE COLUMN + my $is_role_column = select_one +"select count(*) from information_schema.columns where TABLE_NAME='user' AND TABLE_SCHEMA='mysql' and COLUMN_NAME='IS_ROLE'"; + + my $extra_user_condition = ""; + $extra_user_condition = "IS_ROLE = 'N' AND" if $is_role_column > 0; + my @mysqlstatlist; + if ( $is_role_column > 0 ) { + @mysqlstatlist = select_array +"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE IS_ROLE='Y'"; + foreach my $line ( sort @mysqlstatlist ) { + chomp($line); + infoprint "User $line is User Role"; + } + } + else { + debugprint "No Role user detected"; + goodprint "No Role user detected"; + } + # Looking for Anonymous users - my @mysqlstatlist = select_array -"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL"; + @mysqlstatlist = select_array +"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE $extra_user_condition (TRIM(USER) = '' OR USER IS NULL)"; #debugprint Dumper \@mysqlstatlist; #exit 0; if (@mysqlstatlist) { push( @generalrec, - "Remove Anonymous User accounts - there are " + "Remove Anonymous User accounts: there are " . scalar(@mysqlstatlist) . " anonymous accounts." ); foreach my $line ( sort @mysqlstatlist ) { @@ -1969,7 +2219,7 @@ sub security_recommendations { } if ( mysql_version_le( 5, 1 ) ) { badprint "No more password checks for MySQL version <=5.1"; - badprint "MySQL version <=5.1 are deprecated and end of support."; + badprint "MySQL version <=5.1 is deprecated and end of support."; return; } @@ -1977,9 +2227,10 @@ sub security_recommendations { if ( mysql_version_ge( 10, 4 ) ) { @mysqlstatlist = select_array q{SELECT CONCAT(QUOTE(user), '@', QUOTE(host)) FROM mysql.global_priv WHERE - user != '' + ( user != '' AND JSON_CONTAINS(Priv, '"mysql_native_password"', '$.plugin') AND JSON_CONTAINS(Priv, '""', '$.authentication_string') - AND NOT JSON_CONTAINS(Priv, 'true', '$.account_locked')}; + AND NOT JSON_CONTAINS(Priv, 'true', '$.account_locked') + )}; } else { @mysqlstatlist = select_array @@ -2076,7 +2327,7 @@ q{SELECT CONCAT(QUOTE(user), '@', QUOTE(host)) FROM mysql.global_priv WHERE . $pass . "', 2, LENGTH('" . $pass . "'))))"; - debugprint "There is " . scalar(@mysqlstatlist) . " items."; + debugprint "There are " . scalar(@mysqlstatlist) . " items."; if (@mysqlstatlist) { foreach my $line (@mysqlstatlist) { chomp($line); @@ -2118,14 +2369,22 @@ sub get_replication_status { infoprint "Semi synchronous replication Master: " . ( - ( defined( $myvar{'rpl_semi_sync_master_enabled'} ) or defined( $myvar{'rpl_semi_sync_source_enabled'} ) ) - ? ( $myvar{'rpl_semi_sync_master_enabled'} // $myvar{'rpl_semi_sync_source_enabled'} ) + ( + defined( $myvar{'rpl_semi_sync_master_enabled'} ) + or defined( $myvar{'rpl_semi_sync_source_enabled'} ) + ) + ? ( $myvar{'rpl_semi_sync_master_enabled'} + // $myvar{'rpl_semi_sync_source_enabled'} ) : 'Not Activated' ); infoprint "Semi synchronous replication Slave: " . ( - ( defined( $myvar{'rpl_semi_sync_slave_enabled'} ) or defined( $myvar{'rpl_semi_sync_replica_enabled'} ) ) - ? ( $myvar{'rpl_semi_sync_slave_enabled'} // $myvar{'rpl_semi_sync_replica_enabled'} ) + ( + defined( $myvar{'rpl_semi_sync_slave_enabled'} ) + or defined( $myvar{'rpl_semi_sync_replica_enabled'} ) + ) + ? ( $myvar{'rpl_semi_sync_slave_enabled'} + // $myvar{'rpl_semi_sync_replica_enabled'} ) : 'Not Activated' ); if ( scalar( keys %myrepl ) == 0 and scalar( keys %myslaves ) == 0 ) { @@ -2139,11 +2398,16 @@ sub get_replication_status { } $result{'Replication'}{'status'} = \%myrepl; - my ($io_running) = $myrepl{'Slave_IO_Running'} // $myrepl{'Replica_IO_Running'}; + my ($io_running) = $myrepl{'Slave_IO_Running'} + // $myrepl{'Replica_IO_Running'}; debugprint "IO RUNNING: $io_running "; - my ($sql_running) = $myrepl{'Slave_SQL_Running'} // $myrepl{'Replica_SQL_Running'}; + my ($sql_running) = $myrepl{'Slave_SQL_Running'} + // $myrepl{'Replica_SQL_Running'}; debugprint "SQL RUNNING: $sql_running "; - my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'} // $myrepl{'Seconds_Behind_Source'} ; + + my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'} + // $myrepl{'Seconds_Behind_Source'}; + $seconds_behind_master = 1000000 unless defined($seconds_behind_master); debugprint "SECONDS : $seconds_behind_master "; if ( defined($io_running) @@ -2182,24 +2446,30 @@ sub validate_mysql_version { $mysqlverminor ||= 0; $mysqlvermicro ||= 0; - if ( mysql_version_eq(8) - or mysql_version_eq( 5, 7 ) - or mysql_version_eq( 10, 3 ) - or mysql_version_eq( 10, 4 ) + prettyprint " "; + + if ( mysql_version_eq(9) + or mysql_version_eq(8, 4) + or mysql_version_eq(8, 0) or mysql_version_eq( 10, 5 ) or mysql_version_eq( 10, 6 ) - ) + or mysql_version_eq( 10, 11 ) + or mysql_version_eq( 11, 4 ) ) { goodprint "Currently running supported MySQL version " . $myvar{'version'} . ""; return; - } else { + } + else { badprint "Your MySQL version " . $myvar{'version'} - . " is EOL software! Upgrade soon!"; - push ( @generalrec, "You are using n unsupported version for production environments"); - push ( @generalrec, "Upgrade as soon as possible to a supported version !"); - + . " is EOL software. Upgrade soon!"; + push( @generalrec, + "You are using an unsupported version for production environments" + ); + push( @generalrec, + "Upgrade as soon as possible to a supported version !" ); + } } @@ -2249,22 +2519,16 @@ sub mysql_version_le { && int($mysqlvermicro) <= int($mic) ); } -# Checks if MySQL micro version is lower than equal to (major, minor, micro) -sub mysql_micro_version_le { - my ( $maj, $min, $mic ) = @_; - my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) = - $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/; - - return $mysqlvermajor == $maj - && ( $mysqlverminor == $min - && $mysqlvermicro <= $mic ); -} - # Checks for 32-bit boxes with more than 2GB of RAM my ($arch); sub check_architecture { - if ( $doremote eq 1 ) { return; } + if ( is_remote eq 1 ) { + infoprint "Skipping architecture check on remote host"; + infoprint "Using default $opt{defaultarch} bits as target architecture"; + $arch = $opt{defaultarch}; + return; + } if ( `uname` =~ /SunOS/ && `isainfo -b` =~ /64/ ) { $arch = 64; goodprint "Operating on 64-bit architecture"; @@ -2293,7 +2557,7 @@ sub check_architecture { } elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /x86_64/ ) { -# Darwin gibas.local 12.3.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64 +# Darwin gibas.local 12.6.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64 $arch = 64; goodprint "Operating on 64-bit architecture"; } @@ -2337,7 +2601,7 @@ sub check_storage_engines { } elsif ( mysql_version_ge( 5, 1, 5 ) ) { my @engineresults = select_array -"SELECT ENGINE,SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('performance_schema','MyISAM','MERGE','MEMORY') ORDER BY ENGINE ASC"; +"SELECT ENGINE, SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('MyISAM', 'MERGE', 'MEMORY') ORDER BY ENGINE"; foreach my $line (@engineresults) { my ( $engine, $engineenabled ); ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/; @@ -2383,9 +2647,9 @@ sub check_storage_engines { infoprint "Status: $engines"; if ( mysql_version_ge( 5, 1, 5 ) ) { -# MySQL 5 servers can have table sizes calculated quickly from information schema +# MySQL 5+ servers can have table sizes calculated quickly from information schema my @templist = select_array -"SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE),SUM(DATA_LENGTH),SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"; +"SELECT ENGINE, SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(ENGINE), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"; my ( $engine, $size, $count, $dsize, $isize ); foreach my $line (@templist) { @@ -2404,6 +2668,8 @@ sub check_storage_engines { $result{'Engine'}{$engine}{'Data Size'} = $dsize; $result{'Engine'}{$engine}{'Index Size'} = $isize; } + + #print Dumper( \%enginestats ) if $opt{debug}; my $not_innodb = ''; if ( not defined $result{'Variables'}{'innodb_file_per_table'} ) { $not_innodb = "AND NOT ENGINE='InnoDB'"; @@ -2413,7 +2679,7 @@ sub check_storage_engines { } $result{'Tables'}{'Fragmented tables'} = [ select_array -"SELECT CONCAT(CONCAT(TABLE_SCHEMA, '.'), TABLE_NAME),cast(DATA_FREE as signed) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb" +"SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, CAST(DATA_FREE AS SIGNED) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb" ]; $fragtables = scalar @{ $result{'Tables'}{'Fragmented tables'} }; @@ -2448,7 +2714,7 @@ sub check_storage_engines { $fragtables = 0; foreach my $tbl (@tblist) { - #debugprint "Data dump " . Dumper(@$tbl); + #debugprint "Data dump " . Dumper(@$tbl) if $opt{debug}; my ( $engine, $size, $datafree ) = @$tbl; next if $engine eq 'NULL' or not defined($engine); $size = 0 if $size eq 'NULL' or not defined($size); @@ -2478,7 +2744,7 @@ sub check_storage_engines { && defined $myvar{'have_innodb'} && $myvar{'have_innodb'} eq "YES" ) { - badprint "InnoDB is enabled but isn't being used"; + badprint "InnoDB is enabled, but isn't being used"; push( @generalrec, "Add skip-innodb to MySQL configuration to disable InnoDB" ); } @@ -2486,7 +2752,7 @@ sub check_storage_engines { && defined $myvar{'have_bdb'} && $myvar{'have_bdb'} eq "YES" ) { - badprint "BDB is enabled but isn't being used"; + badprint "BDB is enabled, but isn't being used"; push( @generalrec, "Add skip-bdb to MySQL configuration to disable BDB" ); } @@ -2494,30 +2760,36 @@ sub check_storage_engines { && defined $myvar{'have_isam'} && $myvar{'have_isam'} eq "YES" ) { - badprint "MYISAM is enabled but isn't being used"; + badprint "MyISAM is enabled, but isn't being used"; push( @generalrec, -"Add skip-isam to MySQL configuration to disable ISAM (MySQL > 4.1.0)" +"Add skip-isam to MySQL configuration to disable MyISAM (MySQL > 4.1.0)" ); } # Fragmented tables if ( $fragtables > 0 ) { badprint "Total fragmented tables: $fragtables"; - push( @generalrec, - "Run OPTIMIZE TABLE to defragment tables for better performance" ); + push @generalrec, +'Run ALTER TABLE ... FORCE or OPTIMIZE TABLE to defragment tables for better performance'; my $total_free = 0; foreach my $table_line ( @{ $result{'Tables'}{'Fragmented tables'} } ) { - my ( $full_table_name, $data_free ) = split( /\s+/, $table_line ); - $data_free = 0 if ( !defined($data_free) or $data_free eq '' ); + my ( $table_schema, $table_name, $engine, $data_free ) = + split /\t/msx, $table_line; $data_free = $data_free / 1024 / 1024; $total_free += $data_free; - my ( $table_schema, $table_name ) = split( /\./, $full_table_name ); - push( @generalrec, -" OPTIMIZE TABLE `$table_schema`.`$table_name`; -- can free $data_free MB" - ); + my $generalrec; + if ( $engine eq 'InnoDB' ) { + $generalrec = + " ALTER TABLE `$table_schema`.`$table_name` FORCE;"; + } + else { + $generalrec = " OPTIMIZE TABLE `$table_schema`.`$table_name`;"; + } + $generalrec .= " -- can free $data_free MiB"; + push @generalrec, $generalrec; } - push( @generalrec, - "Total freed space after theses OPTIMIZE TABLE : $total_free Mb" ); + push @generalrec, + "Total freed space after defragmentation: $total_free MiB"; } else { goodprint "Total fragmented tables: $fragtables"; @@ -2568,38 +2840,53 @@ sub check_storage_engines { } } } - } my %mycalc; +sub dump_into_file { + my $file = shift; + my $content = shift; + if ( -d "$opt{dumpdir}" ) { + $file = "$opt{dumpdir}/$file"; + open( FILE, ">$file" ) or die "Can't open $file: $!"; + print FILE $content; + close FILE; + infoprint "Data saved to $file"; + } +} + sub calculations { if ( $mystat{'Questions'} < 1 ) { - badprint - "Your server has not answered any queries - cannot continue..."; + badprint "Your server has not answered any queries: cannot continue..."; exit 2; } # Per-thread memory - if ( mysql_version_ge(4) ) { - $mycalc{'per_thread_buffers'} = - $myvar{'read_buffer_size'} + - $myvar{'read_rnd_buffer_size'} + - $myvar{'sort_buffer_size'} + - $myvar{'thread_stack'} + - $myvar{'max_allowed_packet'} + - $myvar{'join_buffer_size'}; - } - else { - $mycalc{'per_thread_buffers'} = - $myvar{'record_buffer'} + - $myvar{'record_rnd_buffer'} + - $myvar{'sort_buffer'} + - $myvar{'thread_stack'} + - $myvar{'join_buffer_size'}; - } + $mycalc{'per_thread_buffers'} = 0; + $mycalc{'per_thread_buffers'} += $myvar{'read_buffer_size'} + if is_int( $myvar{'read_buffer_size'} ); + $mycalc{'per_thread_buffers'} += $myvar{'read_rnd_buffer_size'} + if is_int( $myvar{'read_rnd_buffer_size'} ); + $mycalc{'per_thread_buffers'} += $myvar{'sort_buffer_size'} + if is_int( $myvar{'sort_buffer_size'} ); + $mycalc{'per_thread_buffers'} += $myvar{'thread_stack'} + if is_int( $myvar{'thread_stack'} ); + $mycalc{'per_thread_buffers'} += $myvar{'join_buffer_size'} + if is_int( $myvar{'join_buffer_size'} ); + $mycalc{'per_thread_buffers'} += $myvar{'binlog_cache_size'} + if is_int( $myvar{'binlog_cache_size'} ); + debugprint "per_thread_buffers: $mycalc{'per_thread_buffers'} (" + . human_size( $mycalc{'per_thread_buffers'} ) . " )"; + +# Error max_allowed_packet is not included in thread buffers size +#$mycalc{'per_thread_buffers'} += $myvar{'max_allowed_packet'} if is_int($myvar{'max_allowed_packet'}); + + # Total per-thread memory $mycalc{'total_per_thread_buffers'} = $mycalc{'per_thread_buffers'} * $myvar{'max_connections'}; + + # Max total per-thread memory reached $mycalc{'max_total_per_thread_buffers'} = $mycalc{'per_thread_buffers'} * $mystat{'Max_used_connections'}; @@ -2755,9 +3042,9 @@ sub calculations { } elsif ( mysql_version_ge(5) ) { $mycalc{'total_myisam_indexes'} = select_one -"SELECT IFNULL(SUM(INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';"; +"SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';"; $mycalc{'total_aria_indexes'} = select_one -"SELECT IFNULL(SUM(INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'Aria';"; +"SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'Aria';"; } if ( defined $mycalc{'total_myisam_indexes'} ) { chomp( $mycalc{'total_myisam_indexes'} ); @@ -2838,7 +3125,6 @@ sub calculations { ) ); } - } else { $mycalc{'table_cache_hit_rate'} = 100; @@ -2900,13 +3186,22 @@ sub calculations { # InnoDB $myvar{'innodb_log_files_in_group'} = 1 unless defined( $myvar{'innodb_log_files_in_group'} ); + $myvar{'innodb_log_files_in_group'} = 1 + if $myvar{'innodb_log_files_in_group'} == 0; + $myvar{"innodb_buffer_pool_instances"} = 1 unless defined( $myvar{'innodb_buffer_pool_instances'} ); if ( $myvar{'have_innodb'} eq "YES" ) { - $mycalc{'innodb_log_size_pct'} = - ( $myvar{'innodb_log_file_size'} * - $myvar{'innodb_log_files_in_group'} * 100 / - $myvar{'innodb_buffer_pool_size'} ); + if ( defined $myvar{'innodb_redo_log_capacity'} ) { + $mycalc{'innodb_log_size_pct'} = + ( $myvar{'innodb_redo_log_capacity'} / + $myvar{'innodb_buffer_pool_size'} ) * 100; + } else { + $mycalc{'innodb_log_size_pct'} = + ( $myvar{'innodb_log_file_size'} * + $myvar{'innodb_log_files_in_group'} * 100 / + $myvar{'innodb_buffer_pool_size'} ); + } } if ( !defined $myvar{'innodb_buffer_pool_size'} ) { $mycalc{'innodb_log_size_pct'} = 0; @@ -2921,11 +3216,11 @@ sub calculations { = ( 1, 1 ) unless defined $mystat{'Innodb_buffer_pool_reads'}; $mycalc{'pct_read_efficiency'} = percentage( + $mystat{'Innodb_buffer_pool_read_requests'}, ( - $mystat{'Innodb_buffer_pool_read_requests'} - + $mystat{'Innodb_buffer_pool_read_requests'} + $mystat{'Innodb_buffer_pool_reads'} - ), - $mystat{'Innodb_buffer_pool_read_requests'} + ) ) if defined $mystat{'Innodb_buffer_pool_read_requests'}; debugprint "pct_read_efficiency: " . $mycalc{'pct_read_efficiency'} . ""; debugprint "Innodb_buffer_pool_reads: " @@ -2953,6 +3248,14 @@ sub calculations { $mystat{'Innodb_buffer_pool_pages_total'} ) if defined $mystat{'Innodb_buffer_pool_pages_total'}; + my $lreq = + "select ROUND( 100* sum(allocated)/ " + . $myvar{'innodb_buffer_pool_size'} + . ',1) FROM sys.x\$innodb_buffer_stats_by_table;'; + debugprint("lreq: $lreq"); + $mycalc{'innodb_buffer_alloc_pct'} = select_one($lreq) + if ( $opt{experimental} ); + # Binlog Cache if ( $myvar{'log_bin'} ne 'OFF' ) { $mycalc{'pct_binlog_cache'} = percentage( @@ -2970,7 +3273,7 @@ sub mysql_stats { $qps = sprintf( "%.3f", $mystat{'Questions'} / $mystat{'Uptime'} ); } push( @generalrec, -"MySQL was started within the last 24 hours - recommendations may be inaccurate" +"MySQL was started within the last 24 hours: recommendations may be inaccurate" ) if ( $mystat{'Uptime'} < 86400 ); infoprint "Up for: " . pretty_uptime( $mystat{'Uptime'} ) . " (" @@ -3006,9 +3309,10 @@ sub mysql_stats { . " global + " . hr_bytes( $mycalc{'per_thread_buffers'} ) . " per thread ($myvar{'max_connections'} max threads)"; - infoprint "P_S Max memory usage: " . hr_bytes_rnd( get_pf_memory() ); - $result{'P_S'}{'memory'} = get_pf_memory(); - $result{'P_S'}{'pretty_memory'} = + infoprint "Performance_schema Max memory usage: " + . hr_bytes_rnd( get_pf_memory() ); + $result{'Performance_schema'}{'memory'} = get_pf_memory(); + $result{'Performance_schema'}{'pretty_memory'} = hr_bytes_rnd( get_pf_memory() ); infoprint "Galera GCache Max memory usage: " . hr_bytes_rnd( get_gcache_memory() ); @@ -3094,10 +3398,18 @@ sub mysql_stats { if ( $physical_memory < ( $mycalc{'max_peak_memory'} + get_other_process_memory() ) ) { - badprint - "Overall possible memory usage with other process exceeded memory"; - push( @generalrec, - "Dedicate this server to your database for highest performance." ); + if ( $opt{nondedicated} ) { + infoprint "No warning with --nondedicated option"; + infoprint +"Overall possible memory usage with other process exceeded memory"; + } + else { + badprint +"Overall possible memory usage with other process exceeded memory"; + push( @generalrec, + "Dedicate this server to your database for highest performance." + ); + } } else { goodprint @@ -3128,7 +3440,7 @@ sub mysql_stats { # Connections if ( $mycalc{'pct_connections_used'} > 85 ) { badprint -"Highest connection usage: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; +"Highest connection usage: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})"; push( @adjvars, "max_connections (> " . $myvar{'max_connections'} . ")" ); push( @adjvars, @@ -3146,16 +3458,18 @@ sub mysql_stats { # Aborted Connections if ( $mycalc{'pct_connections_aborted'} > 3 ) { badprint -"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; +"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; push( @generalrec, "Reduce or eliminate unclosed connections and network issues" ); } else { goodprint -"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; +"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})"; } # name resolution + debugprint "skip name resolve: $result{'Variables'}{'skip_name_resolve'}" + if ( defined( $result{'Variables'}{'skip_name_resolve'} ) ); if ( defined( $result{'Variables'}{'skip_networking'} ) && $result{'Variables'}{'skip_networking'} eq 'ON' ) { @@ -3166,24 +3480,29 @@ sub mysql_stats { infoprint "Skipped name resolution test due to missing skip_name_resolve in system variables."; } + #Cpanel and Skip name resolve - elsif ( -r "/usr/local/cpanel/cpanel" ){ - if ( $result{'Variables'}{'skip_name_resolve'} ne 'OFF') { + elsif ( -r "/usr/local/cpanel/cpanel" ) { + if ( $result{'Variables'}{'skip_name_resolve'} ne 'OFF' ) { infoprint "CPanel and Flex system skip-name-resolve should be on"; } - if ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF') { + if ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF' ) { badprint "CPanel and Flex system skip-name-resolve should be on"; - push (@generalrec, "name resolution is enabled due to cPanel doesn't support this disabled."); - push (@adjvars, "skip-name-resolve=0"); + push( @generalrec, +"name resolution is enabled due to cPanel doesn't support this disabled." + ); + push( @adjvars, "skip-name-resolve=0" ); } } - elsif ( $result{'Variables'}{'skip_name_resolve'} eq 'OFF' ) { + elsif ( $result{'Variables'}{'skip_name_resolve'} ne 'ON' + and $result{'Variables'}{'skip_name_resolve'} ne '1' ) + { badprint -"Name resolution is active: a reverse name resolution is made for each new connection and can reduce performance"; +"Name resolution is active: a reverse name resolution is made for each new connection which can reduce performance"; push( @generalrec, -"Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=1" +"Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=ON" ); - push (@adjvars, "skip-name-resolve=1"); + push( @adjvars, "skip-name-resolve=ON" ); } # Query cache @@ -3194,7 +3513,7 @@ sub mysql_stats { "Upgrade MySQL to version 4+ to utilize query caching" ); } elsif ( mysql_version_eq(8) ) { - infoprint "Query cache have been removed in MySQL 8"; + infoprint "Query cache has been removed since MySQL 8.0"; #return; } @@ -3206,13 +3525,9 @@ sub mysql_stats { } elsif ( $mystat{'Com_select'} == 0 ) { badprint - "Query cache cannot be analyzed - no SELECT statements executed"; + "Query cache cannot be analyzed: no SELECT statements executed"; } else { - badprint - "Query cache may be disabled by default due to mutex contention."; - push( @adjvars, "query_cache_size (=0)" ); - push( @adjvars, "query_cache_type (=0)" ); if ( $mycalc{'query_cache_efficiency'} < 20 ) { badprint "Query cache efficiency: $mycalc{'query_cache_efficiency'}% (" @@ -3224,6 +3539,10 @@ sub mysql_stats { "query_cache_limit (> " . hr_bytes_rnd( $myvar{'query_cache_limit'} ) . ", or use smaller result sets)" ); + badprint + "Query cache may be disabled by default due to mutex contention."; + push( @adjvars, "query_cache_size (=0)" ); + push( @adjvars, "query_cache_type (=0)" ); } else { goodprint @@ -3232,30 +3551,31 @@ sub mysql_stats { . " cached / " . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} ) . " selects)"; - } - if ( $mycalc{'query_cache_prunes_per_day'} > 98 ) { - badprint + if ( $mycalc{'query_cache_prunes_per_day'} > 98 ) { + badprint "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; - if ( $myvar{'query_cache_size'} >= 128 * 1024 * 1024 ) { - push( @generalrec, + if ( $myvar{'query_cache_size'} >= 128 * 1024 * 1024 ) { + push( @generalrec, "Increasing the query_cache size over 128M may reduce performance" - ); - push( @adjvars, - "query_cache_size (> " - . hr_bytes_rnd( $myvar{'query_cache_size'} ) - . ") [see warning above]" ); + ); + push( @adjvars, + "query_cache_size (> " + . hr_bytes_rnd( $myvar{'query_cache_size'} ) + . ") [see warning above]" ); + } + else { + push( @adjvars, + "query_cache_size (> " + . hr_bytes_rnd( $myvar{'query_cache_size'} ) + . ")" ); + } } else { - push( @adjvars, - "query_cache_size (> " - . hr_bytes_rnd( $myvar{'query_cache_size'} ) - . ")" ); + goodprint +"Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; } } - else { - goodprint -"Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}"; - } + } # Sorting @@ -3298,8 +3618,7 @@ sub mysql_stats { push( @generalrec, "We will suggest raising the 'join_buffer_size' until JOINs not using indexes are found. - See https://dev.mysql.com/doc/internals/en/join-buffer-size.html - (specially the conclusions at the bottom of the page)." + See https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_join_buffer_size" ); } else { @@ -3344,7 +3663,7 @@ sub mysql_stats { . hr_num( $mystat{'Created_tmp_tables'} ) . " total)"; push( @generalrec, - "Temporary table size is already large - reduce result set size" + "Temporary table size is already large: reduce result set size" ); push( @generalrec, "Reduce your SELECT DISTINCT queries without LIMIT clauses" ); @@ -3448,7 +3767,7 @@ sub mysql_stats { "This is MyISAM only table_cache scalability problem, InnoDB not affected." ); push( @generalrec, - "See more details here: https://bugs.mysql.com/bug.php?id=49177" + "For more details see: https://bugs.mysql.com/bug.php?id=49177" ); push( @generalrec, "This bug already fixed in MySQL 5.7.9 and newer MySQL versions." @@ -3488,7 +3807,7 @@ sub mysql_stats { $mycalc{'total_tables'} = $nbtables; if ( defined $myvar{'table_definition_cache'} ) { if ( $myvar{'table_definition_cache'} == -1 ) { - infoprint( "table_definition_cache(" + infoprint( "table_definition_cache (" . $myvar{'table_definition_cache'} . ") is in autosizing mode" ); } @@ -3497,7 +3816,7 @@ sub mysql_stats { . $myvar{'table_definition_cache'} . ") is less than number of tables ($nbtables) "; push( @adjvars, - "table_definition_cache(" + "table_definition_cache (" . $myvar{'table_definition_cache'} . ") > " . $nbtables . " or -1 (autosizing if supported)" ); @@ -3560,7 +3879,7 @@ sub mysql_stats { . $mystat{'Binlog_cache_use'} . " Total)"; push( @generalrec, - "Increase binlog_cache_size (Actual value: " + "Increase binlog_cache_size (current value: " . $myvar{'binlog_cache_size'} . ")" ); push( @adjvars, @@ -3595,58 +3914,108 @@ sub mysql_stats { # Recommendations for MyISAM sub mysql_myisam { + return 0 unless ( $opt{'myisamstat'} > 0 ); subheaderprint "MyISAM Metrics"; - if ( mysql_version_ge(8) and mysql_version_le(10) ) { - infoprint "MyISAM Metrics are disabled on last MySQL versions."; - if ( $myvar{'key_buffer_size'} > 0) { - push( @adjvars, "key_buffer_size=0" ); - push( @generalrec, "Buffer Key MyISAM set to 0, no MyISAM table detected" ); + my $nb_myisam_tables = select_one( +"SELECT COUNT(*) FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')" + ); + push( @generalrec, + "MyISAM engine is deprecated, consider migrating to InnoDB" ) + if $nb_myisam_tables > 0; + + if ( $nb_myisam_tables > 0 ) { + badprint + "Consider migrating $nb_myisam_tables following tables to InnoDB:"; + my $sql_mig = ""; + for my $myisam_table ( + select_array( +"SELECT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')" + ) + ) + { + $sql_mig = +"${sql_mig}-- InnoDB migration for $myisam_table\nALTER TABLE $myisam_table ENGINE=InnoDB;\n\n"; + infoprint +"* InnoDB migration request for $myisam_table Table: ALTER TABLE $myisam_table ENGINE=InnoDB;"; } - return; + dump_into_file( "migrate_myisam_to_innodb.sql", $sql_mig ); } - my $nb_myisam_tables=select_one( -"SELECT COUNT(*) FROM information_schema.TABLES WHERE ENGINE='MyISAM'" - ); + infoprint("General MyIsam metrics:"); + infoprint " +-- Total MyISAM Tables : $nb_myisam_tables"; + infoprint " +-- Total MyISAM indexes : " + . hr_bytes( $mycalc{'total_myisam_indexes'} ) + if defined( $mycalc{'total_myisam_indexes'} ); + infoprint " +-- KB Size :" . hr_bytes( $myvar{'key_buffer_size'} ); + infoprint " +-- KB Used Size :" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ); + infoprint " +-- KB used :" . $mycalc{'pct_key_buffer_used'} . "%"; + infoprint " +-- Read KB hit rate: $mycalc{'pct_keys_from_mem'}% (" + . hr_num( $mystat{'Key_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_reads'} ) + . " reads)"; + infoprint " +-- Write KB hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; + if ( $nb_myisam_tables == 0 ) { infoprint "No MyISAM table(s) detected ...."; return; } - - # Key buffer usage - if ( $mycalc{'pct_key_buffer_used'} > 0 ) { - if ( $mycalc{'pct_key_buffer_used'} < 90 ) { - badprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * - $myvar{'key_cache_block_size'} ) - . " used / " - . hr_bytes( $myvar{'key_buffer_size'} ) - . " cache)"; - - push( - @adjvars, - "key_buffer_size (\~ " - . hr_num( - $myvar{'key_buffer_size'} * $mycalc{'pct_key_buffer_used'} - / 100 - ) - . ")" - ); - } - else { - goodprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" - . hr_bytes( $myvar{'key_buffer_size'} - - $mystat{'Key_blocks_unused'} * - $myvar{'key_cache_block_size'} ) - . " used / " - . hr_bytes( $myvar{'key_buffer_size'} ) - . " cache)"; + if ( mysql_version_ge(8) and mysql_version_le(10) ) { + infoprint "MyISAM Metrics are disabled since MySQL 8.0."; + if ( $myvar{'key_buffer_size'} > 0 ) { + push( @adjvars, "key_buffer_size=0" ); + push( @generalrec, + "Buffer Key MyISAM set to 0, no MyISAM table detected" ); } + return; } - else { + + if ( !defined( $mycalc{'total_myisam_indexes'} ) ) { + badprint + "Unable to calculate MyISAM index size on MySQL server < 5.0.0"; + push( @generalrec, + "Unable to calculate MyISAM index size on MySQL server < 5.0.0" ); + return; + } + if ( $mycalc{'pct_key_buffer_used'} == 0 ) { # No queries have run that would use keys - debugprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + infoprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) + . " used / " + . hr_bytes( $myvar{'key_buffer_size'} ) + . " cache)"; + infoprint "No SQL statement based on MyISAM table(s) detected ...."; + return; + } + + # Key buffer usage + if ( $mycalc{'pct_key_buffer_used'} < 90 ) { + badprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" + . hr_bytes( $myvar{'key_buffer_size'} - + $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) + . " used / " + . hr_bytes( $myvar{'key_buffer_size'} ) + . " cache)"; + + push( + @adjvars, + "key_buffer_size (\~ " + . hr_num( + $myvar{'key_buffer_size'} * + $mycalc{'pct_key_buffer_used'} / 100 + ) + . ")" + ); + } + else { + goodprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% (" . hr_bytes( $myvar{'key_buffer_size'} - $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} ) . " used / " @@ -3654,81 +4023,72 @@ sub mysql_myisam { . " cache)"; } - # Key buffer - if ( !defined( $mycalc{'total_myisam_indexes'} ) ) { - push( @generalrec, - "Unable to calculate MyISAM index size on MySQL server < 5.0.0" ); + # Key buffer size / total MyISAM indexes + if ( $myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'} + && $mycalc{'pct_keys_from_mem'} < 95 ) + { + badprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + push( @adjvars, + "key_buffer_size (> " + . hr_bytes( $mycalc{'total_myisam_indexes'} ) + . ")" ); } else { - if ( $myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'} - && $mycalc{'pct_keys_from_mem'} < 95 ) - { - badprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; - push( @adjvars, - "key_buffer_size (> " - . hr_bytes( $mycalc{'total_myisam_indexes'} ) - . ")" ); + goodprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + } + if ( $mystat{'Key_read_requests'} > 0 ) { + if ( $mycalc{'pct_keys_from_mem'} < 95 ) { + badprint + "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" + . hr_num( $mystat{'Key_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_reads'} ) + . " reads)"; } else { - goodprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + goodprint + "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" + . hr_num( $mystat{'Key_read_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_reads'} ) + . " reads)"; } - if ( $mystat{'Key_read_requests'} > 0 ) { - if ( $mycalc{'pct_keys_from_mem'} < 95 ) { - badprint - "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" - . hr_num( $mystat{'Key_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_reads'} ) - . " reads)"; - } - else { - goodprint - "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" - . hr_num( $mystat{'Key_read_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_reads'} ) - . " reads)"; - } - } - else { + } - # No queries have run that would use keys - debugprint "Key buffer size / total MyISAM indexes: " - . hr_bytes( $myvar{'key_buffer_size'} ) . "/" - . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; - } - if ( $mystat{'Key_write_requests'} > 0 ) { - if ( $mycalc{'pct_wkeys_from_mem'} < 95 ) { - badprint - "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - } - else { - goodprint - "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" - . hr_num( $mystat{'Key_write_requests'} ) - . " cached / " - . hr_num( $mystat{'Key_writes'} ) - . " writes)"; - } - } - else { - - # No queries have run that would use keys - debugprint + # No queries have run that would use keys + debugprint "Key buffer size / total MyISAM indexes: " + . hr_bytes( $myvar{'key_buffer_size'} ) . "/" + . hr_bytes( $mycalc{'total_myisam_indexes'} ) . ""; + if ( $mystat{'Key_write_requests'} > 0 ) { + if ( $mycalc{'pct_wkeys_from_mem'} < 95 ) { + badprint "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" . hr_num( $mystat{'Key_write_requests'} ) . " cached / " . hr_num( $mystat{'Key_writes'} ) . " writes)"; } + else { + goodprint + "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; + } + } + else { + # No queries have run that would use keys + debugprint + "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% (" + . hr_num( $mystat{'Key_write_requests'} ) + . " cached / " + . hr_num( $mystat{'Key_writes'} ) + . " writes)"; } } @@ -3801,13 +4161,13 @@ sub mariadb_threadpool { if ( $myvar{'thread_pool_size'} < 4 or $myvar{'thread_pool_size'} > 8 ) { badprint -"thread_pool_size between 4 and 8 when using MyIsam storage engine."; +"thread_pool_size between 4 and 8 when using MyISAM storage engine."; push( @generalrec, - "Thread pool size for MyIsam usage (" + "Thread pool size for MyISAM usage (" . $myvar{'thread_pool_size'} . ")" ); push( @adjvars, - "thread_pool_size between 4 and 8 for MyIsam usage" ); + "thread_pool_size between 4 and 8 for MyISAM usage" ); } else { goodprint @@ -3822,41 +4182,41 @@ sub get_pf_memory { return 0 unless defined $myvar{'performance_schema'}; return 0 if $myvar{'performance_schema'} eq 'OFF'; - my @infoPFSMemory = grep /performance_schema.memory/, + my @infoPFSMemory = grep { /\tperformance_schema[.]memory\t/msx } select_array("SHOW ENGINE PERFORMANCE_SCHEMA STATUS"); - return 0 if scalar(@infoPFSMemory) == 0; + @infoPFSMemory == 1 || return 0; $infoPFSMemory[0] =~ s/.*\s+(\d+)$/$1/g; return $infoPFSMemory[0]; } # Recommendations for Performance Schema -sub mysqsl_pfs { +sub mysql_pfs { subheaderprint "Performance schema"; # Performance Schema + debugprint "Performance schema is " . $myvar{'performance_schema'}; $myvar{'performance_schema'} = 'OFF' unless defined( $myvar{'performance_schema'} ); - if ($myvar{'performance_schema'} eq 'OFF') { + if ( $myvar{'performance_schema'} eq 'OFF' ) { badprint "Performance_schema should be activated."; push( @adjvars, "performance_schema=ON" ); push( @generalrec, - "Performance schema should be activated for better diagnostics" - ); - } + "Performance schema should be activated for better diagnostics" ); + } if ( $myvar{'performance_schema'} eq 'ON' ) { infoprint "Performance_schema is activated."; debugprint "Performance schema is " . $myvar{'performance_schema'}; - infoprint "Memory used by P_S: " . hr_bytes( get_pf_memory() ); + infoprint "Memory used by Performance_schema: " + . hr_bytes( get_pf_memory() ); } unless ( grep /^sys$/, select_array("SHOW DATABASES") ) { - infoprint "Sys schema isn't installed."; + infoprint "Sys schema is not installed."; push( @generalrec, -"Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL" + mysql_version_ge( 10, 0 ) + ? "Consider installing Sys schema from https://github.com/FromDual/mariadb-sys for MariaDB" + : "Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL" ) unless ( mysql_version_le( 5, 6 ) ); - push( @generalrec, -"Consider installing Sys schema from https://github.com/FromDual/mariadb-sys for MariaDB" - ) unless ( mysql_version_ge( 10, 0 ) ); return; } @@ -3866,20 +4226,20 @@ sub mysqsl_pfs { infoprint "Sys schema Version: " . select_one("select sys_version from sys.version"); - # Store all sys schema -# for my $pfs_view(select_array('use sys;show tables;')){ - #infoprint "$pfs_view" -# @$result{'sys'}{$pfs_view}{'headers'}=[]; -# for my $h (select_array("select column_name FROM INFORMATION_SCHEMA.COLUMNS c -# WHERE c.table_name = '$pfs_view' ORDER BY c.ORDINAL_POSITION")) { -# push @$result{'sys'}{$pfs_view}{'headers'}, $h; -# } -# exit 1; -# $result{'sys'}{$pfs_view}{'values'}=(); -# for my $lQuery (select_array("select * from sys.$pfs_view")) { -# push $result{'sys'}{$pfs_view}{'values'}, $lQuery; -# } -# } + # Store all sys schema in dumpdir if defined + if ( defined $opt{dumpdir} and -d "$opt{dumpdir}" ) { + for my $sys_view ( select_array('use sys;show tables;') ) { + infoprint "Dumping $sys_view into $opt{dumpdir}"; + my $sys_view_table = $sys_view; + $sys_view_table =~ s/\$/\\\$/g; + select_csv_file( "$opt{dumpdir}/sys_$sys_view.csv", + 'select * from sys.\`' . $sys_view_table . '\`' ); + } + return; + + #exit 0 if ( $opt{stop} == 1 ); + } + # Top user per connection subheaderprint "Performance schema: Top 5 user per connection"; my $nbL = 1; @@ -4662,7 +5022,7 @@ sub mysqsl_pfs { #schema_index_statistics # TOP 15 most read index - subheaderprint "Performance schema: TOP 15 most read indexes"; + subheaderprint "Performance schema: Top 15 most read indexes"; $nbL = 1; for my $lQuery ( select_array( @@ -4752,7 +5112,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); # TOP 15 most read tables - subheaderprint "Performance schema: TOP 15 most read tables"; + subheaderprint "Performance schema: Top 15 most read tables"; $nbL = 1; for my $lQuery ( select_array( @@ -4952,7 +5312,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 reader queries (95% percentile)"; + subheaderprint "Performance schema: Top 15 reader queries (95% percentile)"; $nbL = 1; for my $lQuery ( select_array( @@ -4967,7 +5327,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 most row look queries (95% percentile)"; + "Performance schema: Top 15 most row look queries (95% percentile)"; $nbL = 1; for my $lQuery ( select_array( @@ -4982,7 +5342,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 total latency queries (95% percentile)"; + "Performance schema: Top 15 total latency queries (95% percentile)"; $nbL = 1; for my $lQuery ( select_array( @@ -4997,7 +5357,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 max latency queries (95% percentile)"; + "Performance schema: Top 15 max latency queries (95% percentile)"; $nbL = 1; for my $lQuery ( select_array( @@ -5012,7 +5372,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 average latency queries (95% percentile)"; + "Performance schema: Top 15 average latency queries (95% percentile)"; $nbL = 1; for my $lQuery ( select_array( @@ -5054,7 +5414,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 row sorting queries with sort"; + subheaderprint "Performance schema: Top 15 row sorting queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5068,7 +5428,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 total latency queries with sort"; + subheaderprint "Performance schema: Top 15 total latency queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5082,7 +5442,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 merge queries with sort"; + subheaderprint "Performance schema: Top 15 merge queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5097,7 +5457,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 average sort merges queries with sort"; + "Performance schema: Top 15 average sort merges queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5111,7 +5471,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 scans queries with sort"; + subheaderprint "Performance schema: Top 15 scans queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5125,7 +5485,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 range queries with sort"; + subheaderprint "Performance schema: Top 15 range queries with sort"; $nbL = 1; for my $lQuery ( select_array( @@ -5190,7 +5550,7 @@ sub mysqsl_pfs { if ( $nbL == 1 ); subheaderprint - "Performance schema: TOP 15 total latency queries with temp table"; + "Performance schema: Top 15 total latency queries with temp table"; $nbL = 1; for my $lQuery ( select_array( @@ -5204,7 +5564,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 queries with temp table to disk"; + subheaderprint "Performance schema: Top 15 queries with temp table to disk"; $nbL = 1; for my $lQuery ( select_array( @@ -5221,7 +5581,7 @@ sub mysqsl_pfs { ################################################################################## #wait_classes_global_by_latency -#ysql> select * from wait_classes_global_by_latency; +#mysql> select * from wait_classes_global_by_latency; #-----------------+-------+---------------+-------------+-------------+-------------+ # event_class | total | total_latency | min_latency | avg_latency | max_latency | #-----------------+-------+---------------+-------------+-------------+-------------+ @@ -5231,7 +5591,7 @@ sub mysqsl_pfs { #-----------------+-------+---------------+-------------+-------------+-------------+ # rows in set (0,00 sec) - subheaderprint "Performance schema: TOP 15 class events by number"; + subheaderprint "Performance schema: Top 15 class events by number"; $nbL = 1; for my $lQuery ( select_array( @@ -5245,7 +5605,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 30 events by number"; + subheaderprint "Performance schema: Top 30 events by number"; $nbL = 1; for my $lQuery ( select_array( @@ -5259,7 +5619,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 class events by total latency"; + subheaderprint "Performance schema: Top 15 class events by total latency"; $nbL = 1; for my $lQuery ( select_array( @@ -5273,7 +5633,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 30 events by total latency"; + subheaderprint "Performance schema: Top 30 events by total latency"; $nbL = 1; for my $lQuery ( select_array( @@ -5287,7 +5647,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 15 class events by max latency"; + subheaderprint "Performance schema: Top 15 class events by max latency"; $nbL = 1; for my $lQuery ( select_array( @@ -5301,7 +5661,7 @@ sub mysqsl_pfs { infoprint "No information found or indicators deactivated." if ( $nbL == 1 ); - subheaderprint "Performance schema: TOP 30 events by max latency"; + subheaderprint "Performance schema: Top 30 events by max latency"; $nbL = 1; for my $lQuery ( select_array( @@ -5393,7 +5753,7 @@ sub mariadb_tokudb { } infoprint "TokuDB is enabled."; - # All is to done here + # Not implemented } # Recommendations for XtraDB @@ -5410,7 +5770,7 @@ sub mariadb_xtradb { infoprint "XtraDB is enabled."; infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." - # All is to done here + # Not implemented } # Recommendations for RocksDB @@ -5426,7 +5786,7 @@ sub mariadb_rockdb { } infoprint "RocksDB is enabled."; - # All is to do here + # Not implemented } # Recommendations for Spider @@ -5442,7 +5802,7 @@ sub mariadb_spider { } infoprint "Spider is enabled."; - # All is to do here + # Not implemented } # Recommendations for Connect @@ -5458,7 +5818,7 @@ sub mariadb_connect { } infoprint "Connect is enabled."; - # All is to do here + # Not implemented } # Perl trim function to remove whitespace from the start and end of the string @@ -5479,7 +5839,7 @@ sub get_wsrep_options { @galera_options = remove_cr @galera_options; @galera_options = remove_empty @galera_options; - #debugprint Dumper( \@galera_options ); + #debugprint Dumper( \@galera_options ) if $opt{debug}; return @galera_options; } @@ -5502,6 +5862,118 @@ sub get_wsrep_option { return $memValue; } +# REcommendations for Tables +sub mysql_table_structures { + return 0 unless ( $opt{structstat} > 0 ); + subheaderprint "Table structures analysis"; + + my @primaryKeysNbTables = select_array( + "Select CONCAT(c.table_schema, ',' , c.table_name) +from information_schema.columns c +join information_schema.tables t using (TABLE_SCHEMA, TABLE_NAME) +where c.table_schema not in ('sys', 'mysql', 'information_schema', 'performance_schema') + and t.table_type = 'BASE TABLE' +group by c.table_schema,c.table_name +having sum(if(c.column_key in ('PRI', 'UNI'), 1, 0)) = 0" + ); + + my $tmpContent = 'Schema,Table'; + if ( scalar(@primaryKeysNbTables) > 0 ) { + badprint "Following table(s) don't have primary key:"; + foreach my $badtable (@primaryKeysNbTables) { + badprint "\t$badtable"; + push @{ $result{'Tables without PK'} }, $badtable; + $tmpContent .= "\n$badtable"; + } + push @generalrec, +"Ensure that all table(s) get an explicit primary keys for performance, maintenance and also for replication"; + + } + else { + goodprint "All tables get a primary key"; + } + dump_into_file( "tables_without_primary_keys.csv", $tmpContent ); + + my @nonInnoDBTables = select_array( + "select CONCAT(table_schema, ',', table_name, ',', ENGINE) +FROM information_schema.tables t +WHERE ENGINE <> 'InnoDB' +and t.table_type = 'BASE TABLE' +and table_schema not in +('sys', 'mysql', 'performance_schema', 'information_schema')" + ); + $tmpContent = 'Schema,Table,Engine'; + if ( scalar(@nonInnoDBTables) > 0 ) { + badprint "Following table(s) are not InnoDB table:"; + push @generalrec, +"Ensure that all table(s) are InnoDB tables for performance and also for replication"; + foreach my $badtable (@nonInnoDBTables) { + if ( $badtable =~ /Memory/i ) { + badprint +"Table $badtable is a MEMORY table. It's suggested to use only InnoDB tables in production"; + } + else { + badprint "\t$badtable"; + } + $tmpContent .= "\n$badtable"; + } + } + else { + goodprint "All tables are InnoDB tables"; + } + dump_into_file( "tables_non_innodb.csv", $tmpContent ); + + my @nonutf8columns = select_array( +"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH) +from information_schema.columns +WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') +and (CHARacter_set_name NOT LIKE 'utf8%' +or COLLATION_name NOT LIKE 'utf8%');" + ); + $tmpContent = + 'Schema,Table,Column, Charset, Collation, Data Type, Max Length'; + if ( scalar(@nonutf8columns) > 0 ) { + badprint "Following character columns(s) are not utf8 compliant:"; + push @generalrec, +"Ensure that all text colums(s) are UTF-8 compliant for encoding support and performance"; + foreach my $badtable (@nonutf8columns) { + badprint "\t$badtable"; + $tmpContent .= "\n$badtable"; + } + } + else { + goodprint "All columns are UTF-8 compliant"; + } + dump_into_file( "columns_non_utf8.csv", $tmpContent ); + + my @utf8columns = select_array( +"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH) +from information_schema.columns +WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') +and (CHARacter_set_name LIKE 'utf8%' +or COLLATION_name LIKE 'utf8%');" + ); + $tmpContent = + 'Schema,Table,Column, Charset, Collation, Data Type, Max Length'; + foreach my $badtable (@utf8columns) { + $tmpContent .= "\n$badtable"; + } + dump_into_file( "columns_utf8.csv", $tmpContent ); + + my @ftcolumns = select_array( +"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', data_type) +from information_schema.columns +WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema') +AND data_type='FULLTEXT';" + ); + $tmpContent = 'Schema,Table,Column, Data Type'; + foreach my $ctable (@ftcolumns) { + $tmpContent .= "\n$ctable"; + } + dump_into_file( "fulltext_columns.csv", $tmpContent ); + +} + # Recommendations for Galera sub mariadb_galera { subheaderprint "Galera Metrics"; @@ -5540,30 +6012,19 @@ sub mariadb_galera { infoprint "GCache is using " . hr_bytes_rnd( get_wsrep_option('gcache.mem_size') ); - #my @primaryKeysNbTables=(); - my @primaryKeysNbTables = select_array( - "Select CONCAT(c.table_schema,CONCAT('.', c.table_name)) -from information_schema.columns c -join information_schema.tables t using (TABLE_SCHEMA, TABLE_NAME) -where c.table_schema not in ('mysql', 'information_schema', 'performance_schema') - and t.table_type != 'VIEW' -group by c.table_schema,c.table_name -having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" - ); - - infoprint "CPU core detected : " . (cpu_cores); + infoprint "CPU cores detected : " . (cpu_cores); infoprint "wsrep_slave_threads: " . get_wsrep_option('wsrep_slave_threads'); if ( get_wsrep_option('wsrep_slave_threads') > ( (cpu_cores) * 4 ) or get_wsrep_option('wsrep_slave_threads') < ( (cpu_cores) * 2 ) ) { badprint -"wsrep_slave_threads is not equal to 2, 3 or 4 times number of CPU(s)"; +"wsrep_slave_threads is not equal to 2, 3 or 4 times the number of CPU(s)"; push @adjvars, "wsrep_slave_threads = " . ( (cpu_cores) * 4 ); } else { goodprint - "wsrep_slave_threads is equal to 2, 3 or 4 times number of CPU(s)"; +"wsrep_slave_threads is equal to 2, 3 or 4 times the number of CPU(s)"; } if ( get_wsrep_option('wsrep_slave_threads') > 1 ) { @@ -5613,33 +6074,9 @@ having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" } else { goodprint -"Flow control fraction seems to be OK (wsrep_flow_control_paused<=0.02)"; +"Flow control fraction seems to be OK (wsrep_flow_control_paused <= 0.02)"; } - if ( scalar(@primaryKeysNbTables) > 0 ) { - badprint "Following table(s) don't have primary key:"; - foreach my $badtable (@primaryKeysNbTables) { - badprint "\t$badtable"; - push @{ $result{'Tables without PK'} }, $badtable; - } - } - else { - goodprint "All tables get a primary key"; - } - my @nonInnoDBTables = select_array( -"select CONCAT(table_schema,CONCAT('.', table_name)) from information_schema.tables where ENGINE <> 'InnoDB' and table_schema not in ('mysql', 'performance_schema', 'information_schema')" - ); - if ( scalar(@nonInnoDBTables) > 0 ) { - badprint "Following table(s) are not InnoDB table:"; - push @generalrec, - "Ensure that all table(s) are InnoDB tables for Galera replication"; - foreach my $badtable (@nonInnoDBTables) { - badprint "\t$badtable"; - } - } - else { - goodprint "All tables are InnoDB tables"; - } if ( $myvar{'binlog_format'} ne 'ROW' ) { badprint "Binlog format should be in ROW mode."; push @adjvars, "binlog_format = ROW"; @@ -5682,7 +6119,7 @@ having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" # wsrep_cluster_address doesn't include garbd nodes if ( $nbNodes > $nbNodesSize ) { badprint -"All cluster nodes are not detected. wsrep_cluster_size less then node count in wsrep_cluster_address"; +"All cluster nodes are not detected. wsrep_cluster_size less than node count in wsrep_cluster_address"; } else { goodprint "All cluster nodes detected."; @@ -5720,7 +6157,8 @@ having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" } else { badprint "Galera Notify command is not defined."; - push( @adjvars, "set up parameter wsrep_notify_cmd to be notify" ); + push( @adjvars, + "set up parameter wsrep_notify_cmd to be notified" ); } if ( trim( $myvar{'wsrep_sst_method'} ) !~ "^xtrabackup.*" and trim( $myvar{'wsrep_sst_method'} ) !~ "^mariabackup" ) @@ -5812,7 +6250,7 @@ having sum(if(c.column_key in ('PRI','UNI'), 1,0)) = 0" } } - #debugprint Dumper get_wsrep_options(); + #debugprint Dumper get_wsrep_options() if $opt{debug}; } # Recommendations for InnoDB @@ -5866,35 +6304,46 @@ sub mysql_innodb { infoprint " +-- InnoDB Additional Mem Pool: " . hr_bytes( $myvar{'innodb_additional_mem_pool_size'} ) . ""; } - if ( defined $myvar{'innodb_log_file_size'} ) { - infoprint " +-- InnoDB Log File Size: " - . hr_bytes( $myvar{'innodb_log_file_size'} ); + if ( defined $myvar{'innodb_redo_log_capacity'} ) { + infoprint " +-- InnoDB Redo Log Capacity: " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); } - if ( defined $myvar{'innodb_log_files_in_group'} ) { - infoprint " +-- InnoDB Log File In Group: " - . $myvar{'innodb_log_files_in_group'}; - } - if ( defined $myvar{'innodb_log_files_in_group'} ) { - infoprint " +-- InnoDB Total Log File Size: " - . hr_bytes( $myvar{'innodb_log_files_in_group'} * - $myvar{'innodb_log_file_size'} ) - . "(" - . $mycalc{'innodb_log_size_pct'} - . " % of buffer pool)"; + else { + if ( defined $myvar{'innodb_log_file_size'} ) { + infoprint " +-- InnoDB Log File Size: " + . hr_bytes( $myvar{'innodb_log_file_size'} ); + } + if ( defined $myvar{'innodb_log_files_in_group'} ) { + infoprint " +-- InnoDB Log File In Group: " + . $myvar{'innodb_log_files_in_group'}; + infoprint " +-- InnoDB Total Log File Size: " + . hr_bytes( $myvar{'innodb_log_files_in_group'} * + $myvar{'innodb_log_file_size'} ) + . "(" + . $mycalc{'innodb_log_size_pct'} + . " % of buffer pool)"; + } + else { + infoprint " +-- InnoDB Total Log File Size: " + . hr_bytes( $myvar{'innodb_log_file_size'} ) . "(" + . $mycalc{'innodb_log_size_pct'} + . " % of buffer pool)"; + } } if ( defined $myvar{'innodb_log_buffer_size'} ) { infoprint " +-- InnoDB Log Buffer: " . hr_bytes( $myvar{'innodb_log_buffer_size'} ); } if ( defined $mystat{'Innodb_buffer_pool_pages_free'} ) { - infoprint " +-- InnoDB Log Buffer Free: " + infoprint " +-- InnoDB Buffer Free: " . hr_bytes( $mystat{'Innodb_buffer_pool_pages_free'} ) . ""; } if ( defined $mystat{'Innodb_buffer_pool_pages_total'} ) { - infoprint " +-- InnoDB Log Buffer Used: " + infoprint " +-- InnoDB Buffer Used: " . hr_bytes( $mystat{'Innodb_buffer_pool_pages_total'} ) . ""; } } + if ( defined $myvar{'innodb_thread_concurrency'} ) { infoprint "InnoDB Thread Concurrency: " . $myvar{'innodb_thread_concurrency'}; @@ -5910,53 +6359,143 @@ sub mysql_innodb { } # InnoDB Buffer Pool Size + if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} > 4294967295 ) { + badprint + "InnoDB Buffer Pool size limit reached for 32 bits architecture: (" + . hr_bytes(4294967295) . " )"; + push( @adjvars, + "limit innodb_buffer_pool_size under " + . hr_bytes(4294967295) + . " for 32 bits architecture" ); + } + if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} < 4294967295 ) { + goodprint "InnoDB Buffer Pool size ( " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " ) under limit for 32 bits architecture: (" + . hr_bytes(4294967295) . ")"; + } + if ( $arch == 64 + && $myvar{'innodb_buffer_pool_size'} > 18446744073709551615 ) + { + badprint "InnoDB Buffer Pool size limit(" + . hr_bytes(18446744073709551615) + . ") reached for 64 bits architecture"; + push( @adjvars, + "limit innodb_buffer_pool_size under " + . hr_bytes(18446744073709551615) + . " for 64 bits architecture" ); + } + + if ( $arch == 64 + && $myvar{'innodb_buffer_pool_size'} < 18446744073709551615 ) + { + goodprint "InnoDB Buffer Pool size ( " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " ) under limit for 64 bits architecture: (" + . hr_bytes(18446744073709551615) . " )"; + } if ( $myvar{'innodb_buffer_pool_size'} > $enginestats{'InnoDB'} ) { goodprint "InnoDB buffer pool / data size: " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / " . hr_bytes( $enginestats{'InnoDB'} ) . ""; } else { badprint "InnoDB buffer pool / data size: " - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / " . hr_bytes( $enginestats{'InnoDB'} ) . ""; push( @adjvars, "innodb_buffer_pool_size (>= " . hr_bytes( $enginestats{'InnoDB'} ) . ") if possible." ); } + + # select round( 100* sum(allocated)/( select VARIABLE_VALUE + # FROM information_schema.global_variables + # where VARIABLE_NAME='innodb_buffer_pool_size' ) + # ,2) as "PCT ALLOC/BUFFER POOL" + #from sys.x$innodb_buffer_stats_by_table; + + if ( $opt{experimental} ) { + debugprint( 'innodb_buffer_alloc_pct: "' + . $mycalc{innodb_buffer_alloc_pct} + . '"' ); + if ( defined $mycalc{innodb_buffer_alloc_pct} + and $mycalc{innodb_buffer_alloc_pct} ne '' ) + { + if ( $mycalc{innodb_buffer_alloc_pct} < 80 ) { + badprint "Ratio Buffer Pool allocated / Buffer Pool Size: " + . $mycalc{'innodb_buffer_alloc_pct'} . '%'; + } + else { + goodprint "Ratio Buffer Pool allocated / Buffer Pool Size: " + . $mycalc{'innodb_buffer_alloc_pct'} . '%'; + } + } + } if ( $mycalc{'innodb_log_size_pct'} < 20 or $mycalc{'innodb_log_size_pct'} > 30 ) { - badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" - . $mycalc{'innodb_log_size_pct'} . "%): " - . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " - . $myvar{'innodb_log_files_in_group'} . "/" - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; - push( - @adjvars, - "innodb_log_file_size should be (=" - . hr_bytes_rnd( - $myvar{'innodb_buffer_pool_size'} / - $myvar{'innodb_log_files_in_group'} / 4 - ) - . ") if possible, so InnoDB total log files size equals 25% of buffer pool size." - ); - if ( mysql_version_le( 5, 6, 2 ) ) { + if ( defined $myvar{'innodb_redo_log_capacity'} ) { + badprint + "Ratio InnoDB redo log capacity / InnoDB Buffer pool size (" + . $mycalc{'innodb_log_size_pct'} . "%): " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . " / " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + push( @adjvars, + "innodb_redo_log_capacity should be (=" + . hr_bytes_rnd( $myvar{'innodb_buffer_pool_size'} / 4 ) + . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." + ); push( @generalrec, -"For MySQL 5.6.2 and lower, Max combined innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." +"Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" ); } - push( @generalrec, -"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" - ); + else { + badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" + . $mycalc{'innodb_log_size_pct'} . "%): " + . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " + . $myvar{'innodb_log_files_in_group'} . " / " + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + push( + @adjvars, + "innodb_log_file_size should be (=" + . hr_bytes_rnd( + $myvar{'innodb_buffer_pool_size'} / + $myvar{'innodb_log_files_in_group'} / 4 + ) + . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." + ); + push( @generalrec, +"Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" + ); + } + if ( mysql_version_le( 5, 6, 2 ) ) { + push( @generalrec, +"For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." + ); + } + } else { - goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " - . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " - . $myvar{'innodb_log_files_in_group'} . "/" - . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) - . " should be equal to 25%"; + if ( defined $myvar{'innodb_redo_log_capacity'} ) { + goodprint + "Ratio InnoDB Redo Log Capacity / InnoDB Buffer pool size: " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + } + else { + push( @generalrec, +"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" + ); + goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " + . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " + . $myvar{'innodb_log_files_in_group'} . "/" + . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) + . " should be equal to 25%"; + } } # InnoDB Buffer Pool Instances (MySQL 5.6.6+) @@ -6051,19 +6590,19 @@ sub mysql_innodb { { badprint "InnoDB Read buffer efficiency: " . $mycalc{'pct_read_efficiency'} . "% (" - . ( $mystat{'Innodb_buffer_pool_read_requests'} - - $mystat{'Innodb_buffer_pool_reads'} ) - . " hits/ " . $mystat{'Innodb_buffer_pool_read_requests'} + . " hits / " + . ( $mystat{'Innodb_buffer_pool_reads'} + + $mystat{'Innodb_buffer_pool_read_requests'} ) . " total)"; } else { goodprint "InnoDB Read buffer efficiency: " . $mycalc{'pct_read_efficiency'} . "% (" - . ( $mystat{'Innodb_buffer_pool_read_requests'} - - $mystat{'Innodb_buffer_pool_reads'} ) - . " hits/ " . $mystat{'Innodb_buffer_pool_read_requests'} + . " hits / " + . ( $mystat{'Innodb_buffer_pool_reads'} + + $mystat{'Innodb_buffer_pool_read_requests'} ) . " total)"; } @@ -6075,16 +6614,20 @@ sub mysql_innodb { . abs( $mycalc{'pct_write_efficiency'} ) . "% (" . abs( $mystat{'Innodb_log_write_requests'} - $mystat{'Innodb_log_writes'} ) - . " hits/ " + . " hits / " . $mystat{'Innodb_log_write_requests'} . " total)"; + push( @adjvars, + "innodb_log_buffer_size (> " + . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} ) + . ")" ); } else { - goodprint "InnoDB Write log efficiency: " + goodprint "InnoDB Write Log efficiency: " . $mycalc{'pct_write_efficiency'} . "% (" . ( $mystat{'Innodb_log_write_requests'} - $mystat{'Innodb_log_writes'} ) - . " hits/ " + . " hits / " . $mystat{'Innodb_log_write_requests'} . " total)"; } @@ -6093,7 +6636,8 @@ sub mysql_innodb { $mystat{'Innodb_log_waits_computed'} = 0; if ( defined( $mystat{'Innodb_log_waits'} ) - and defined( $mystat{'Innodb_log_writes'} ) ) + and defined( $mystat{'Innodb_log_writes'} ) + and $mystat{'Innodb_log_writes'} > 0.000001 ) { $mystat{'Innodb_log_waits_computed'} = $mystat{'Innodb_log_waits'} / $mystat{'Innodb_log_writes'}; @@ -6156,7 +6700,7 @@ sub mysql_databases { subheaderprint "Database Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint -"Skip Database metrics from information schema missing in this version"; +"Database metrics from information schema are missing in this version. Skipping..."; return; } @@ -6166,20 +6710,20 @@ sub mysql_databases { infoprint "There is " . scalar(@dblist) . " Database(s)."; my @totaldbinfo = split /\s/, select_one( -"SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" +"SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" ); infoprint "All User Databases:"; infoprint " +-- TABLE : " . select_one( -"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" ) . ""; infoprint " +-- VIEW : " . select_one( -"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" ) . ""; infoprint " +-- INDEX : " . select_one( -"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' )" +"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')" ) . ""; infoprint " +-- CHARS : " @@ -6187,7 +6731,7 @@ sub mysql_databases { . ( join ", ", select_array( -"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" +"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" ) ) . ")"; infoprint " +-- COLLA : " @@ -6195,7 +6739,7 @@ sub mysql_databases { . ( join ", ", select_array( -"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_COLLATION IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" +"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_COLLATION IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" ) ) . ")"; infoprint " +-- ROWS : " @@ -6212,7 +6756,7 @@ sub mysql_databases { . ( join ", ", select_array( -"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE ENGINE IS NOT NULL AND TABLE_SCHEMA NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );" +"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE ENGINE IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys');" ) ) . ")"; @@ -6226,18 +6770,21 @@ sub mysql_databases { percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%"; $result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3]; print "\n" unless ( $opt{'silent'} or $opt{'json'} ); + my $nbViews = 0; + my $nbTables = 0; foreach (@dblist) { my @dbinfo = split /\s/, select_one( -"SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) , SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE),COUNT(TABLE_NAME),COUNT(DISTINCT(TABLE_COLLATION)),COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA" +"SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA" ); next unless defined $dbinfo[0]; + infoprint "Database: " . $dbinfo[0] . ""; - infoprint " +-- TABLE : " - . select_one( + $nbTables = select_one( "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$_'" - ) . ""; + ); + infoprint " +-- TABLE : $nbTables"; infoprint " +-- VIEW : " . select_one( "SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA='$_'" @@ -6292,11 +6839,16 @@ sub mysql_databases { "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbinfo[0]' AND ENGINE='$eng'" ) . " TABLE(s)"; } + + if ( $nbTables == 0 ) { + badprint " No table in $dbinfo[0] database"; + next; + } badprint "Index size is larger than data size for $dbinfo[0] \n" if ( $dbinfo[2] ne 'NULL' ) and ( $dbinfo[3] ne 'NULL' ) and ( $dbinfo[2] < $dbinfo[3] ); - unless ( $dbinfo[5] == 1 ) { + if ( $dbinfo[5] > 1 and $nbTables > 0 ) { badprint "There are " . $dbinfo[5] . " storage engines. Be careful. \n"; @@ -6387,16 +6939,33 @@ sub mysql_tables { subheaderprint "Table Column Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint -"Skip Database metrics from information schema missing in this version"; +"Table column metrics from information schema are missing in this version. Skipping..."; return; } if ( mysql_version_ge(8) and not mysql_version_eq(10) ) { infoprint -"MySQL and Percona version 8 and greater have remove PROCEDURE ANALYSE feature"; +"MySQL and Percona version 8.0 and greater have removed PROCEDURE ANALYSE feature"; $opt{colstat} = 0; infoprint "Disabling colstat parameter"; } + + infoprint("Dumpdir: $opt{dumpdir}"); + + # Store all information schema in dumpdir if defined + if ( defined $opt{dumpdir} and -d "$opt{dumpdir}" ) { + for my $info_s_table ( + select_array('use information_schema;show tables;') ) + { + infoprint "Dumping $info_s_table into $opt{dumpdir}"; + select_csv_file( + "$opt{dumpdir}/ifs_${info_s_table}.csv", + "select * from information_schema.$info_s_table" + ); + } + + #exit 0 if ( $opt{stop} == 1 ); + } foreach ( select_user_dbs() ) { my $dbname = $_; next unless defined $_; @@ -6495,7 +7064,7 @@ sub mysql_indexes { subheaderprint "Indexes Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint - "Skip Index metrics from information schema missing in this version"; +"Index metrics from information schema are missing in this version. Skipping..."; return; } @@ -6506,8 +7075,8 @@ sub mysql_indexes { # } my $selIdxReq = <<'ENDSQL'; SELECT - CONCAT(t.TABLE_SCHEMA, '.',t.TABLE_NAME) AS 'table', - CONCAT(s.INDEX_NAME, '(',s.COLUMN_NAME, ')') AS 'index' + CONCAT(t.TABLE_SCHEMA, '.', t.TABLE_NAME) AS 'table', + CONCAT(s.INDEX_NAME, '(', s.COLUMN_NAME, ')') AS 'index' , s.SEQ_IN_INDEX AS 'seq' , s2.max_columns AS 'maxcol' , s.CARDINALITY AS 'card' @@ -6569,7 +7138,7 @@ ENDSQL foreach my $dbname ( select_user_dbs() ) { infoprint "Database: " . $dbname . ""; $selIdxReq = <<"ENDSQL"; - SELECT concat(table_name,'.', index_name) AS idxname, + SELECT concat(table_name, '.', index_name) AS idxname, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols, SUM(CARDINALITY) as card, INDEX_TYPE as type @@ -6589,21 +7158,25 @@ ENDSQL infoprint " +-- COMMENT : " . $info[5] if defined $info[5]; $found++; } - badprint "No index found for $dbname database" if $found == 0; + my $nbTables = select_one( +"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$dbname'" + ); + badprint "No index found for $dbname database" + if $found == 0 and $nbTables > 1; push @generalrec, "Add indexes on tables from $dbname database" - if $found == 0; + if $found == 0 and $nbTables > 1; } return unless ( defined( $myvar{'performance_schema'} ) and $myvar{'performance_schema'} eq 'ON' ); $selIdxReq = <<'ENDSQL'; -SELECT CONCAT(object_schema,'.',object_name) AS 'table', index_name +SELECT CONCAT(object_schema, '.', object_name) AS 'table', index_name FROM performance_schema.table_io_waits_summary_by_index_usage WHERE index_name IS NOT NULL -AND count_star =0 +AND count_star = 0 AND index_name <> 'PRIMARY' -AND object_schema NOT IN ( 'mysql', 'performance_schema', 'information_schema' ) +AND object_schema NOT IN ('mysql', 'performance_schema', 'information_schema') ORDER BY count_star, object_schema, object_name; ENDSQL @idxinfo = select_array($selIdxReq); @@ -6618,36 +7191,36 @@ ENDSQL } } -sub mysql_views() { +sub mysql_views { subheaderprint "Views Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint - "Skip Index metrics from information schema missing in this version"; +"Views metrics from information schema are missing in this version. Skipping..."; return; } } -sub mysql_routines() { +sub mysql_routines { subheaderprint "Routines Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint - "Skip Index metrics from information schema missing in this version"; +"Routines metrics from information schema are missing in this version. Skipping..."; return; } } -sub mysql_triggers() { +sub mysql_triggers { subheaderprint "Triggers Metrics"; unless ( mysql_version_ge( 5, 5 ) ) { infoprint - "Skip Index metrics from information schema missing in this version"; +"Trigger metrics from information schema are missing in this version. Skipping..."; return; } } # Take the two recommendation arrays and display them at the end of the output sub make_recommendations { - $result{'Recommendations'} = \@generalrec; + $result{'Recommendations'} = \@generalrec; $result{'AdjustVariables'} = \@adjvars; subheaderprint "Recommendations"; if ( @generalrec > 0 ) { @@ -6673,12 +7246,13 @@ sub close_outputfile { } sub headerprint { - prettyprint - " >> MySQLTuner $tunerversion\n" + prettyprint " >> MySQLTuner $tunerversion\n" . "\t * Jean-Marie Renouard \n" . "\t * Major Hayden \n" . " >> Bug reports, feature requests, and downloads at http://mysqltuner.pl/\n" . " >> Run with '--help' for additional options and output filtering"; + debugprint( "Debug: " . $opt{debug} ); + debugprint( "Experimental: " . $opt{experimental} ); } sub string2file { @@ -6689,12 +7263,12 @@ sub string2file { "Unable to open $filename in write mode. Please check permissions for this file or directory"; print $fh $content if defined($content); close $fh; - debugprint $content if ( $opt{'debug'} ); + debugprint $content; } sub file2array { my $filename = shift; - debugprint "* reading $filename" if ( $opt{'debug'} ); + debugprint "* reading $filename"; my $fh; open( $fh, q(<), "$filename" ) or die "Couldn't open $filename for reading: $!\n"; @@ -6809,7 +7383,7 @@ sub which { # --------------------------------------------------------------------------- headerprint; # Header Print -validate_tuner_version; # Check last version +validate_tuner_version; # Check latest version mysql_setup; # Gotta login first debugprint "MySQL FINAL Client : $mysqlcmd $mysqllogin"; debugprint "MySQL Admin FINAL Client : $mysqladmincmd $mysqllogin"; @@ -6817,33 +7391,46 @@ debugprint "MySQL Admin FINAL Client : $mysqladmincmd $mysqllogin"; #exit(0); os_setup; # Set up some OS variables get_all_vars; # Toss variables/status into hashes -get_tuning_info; # Get information about the tuning connexion +get_tuning_info; # Get information about the tuning connection +calculations; # Calculate everything we need +check_architecture; # Suggest 64-bit upgrade +check_storage_engines; # Show enabled storage engines +if ( $opt{'feature'} ne '' ) { + subheaderprint "See FEATURES.md for more information"; + no strict 'refs'; + for my $feature ( split /,/, $opt{'feature'} ) { + subheaderprint "Running feature: $opt{'feature'}"; + $feature->(); + } + make_recommendations; + exit(0); +} validate_mysql_version; # Check current MySQL version -check_architecture; # Suggest 64-bit upgrade -system_recommendations; # avoid to many service on the same host +system_recommendations; # Avoid too many services on the same host log_file_recommendations; # check log file content -check_storage_engines; # Show enabled storage engines -check_metadata_perf; # Show parameter impacting performance during analysis -mysql_databases; # Show informations about databases -mysql_tables; # Show informations about table column +check_metadata_perf; # Show parameter impacting performance during analysis +mysql_databases; # Show information about databases +mysql_tables; # Show information about table column +mysql_table_structures; # Show information about table structures -mysql_indexes; # Show informations about indexes -mysql_views; # Show informations about views -mysql_triggers; # Show informations about triggers -mysql_routines; # Show informations about routines -security_recommendations; # Display some security recommendations -cve_recommendations; # Display related CVE -calculations; # Calculate everything we need -mysql_stats; # Print the server stats -mysqsl_pfs; # Print Performance schema info -mariadb_threadpool; # Print MariaDB ThreadPool stats -mysql_myisam; # Print MyISAM stats -mysql_innodb; # Print InnoDB stats -mariadb_aria; # Print MariaDB Aria stats -mariadb_tokudb; # Print MariaDB Tokudb stats -mariadb_xtradb; # Print MariaDB XtraDB stats +mysql_indexes; # Show information about indexes +mysql_views; # Show information about views +mysql_triggers; # Show information about triggers +mysql_routines; # Show information about routines +security_recommendations; # Display some security recommendations +cve_recommendations; # Display related CVE + +mysql_stats; # Print the server stats +mysql_pfs; # Print Performance schema info + +mariadb_threadpool; # Print MariaDB ThreadPool stats +mysql_myisam; # Print MyISAM stats +mysql_innodb; # Print InnoDB stats +mariadb_aria; # Print MariaDB Aria stats +mariadb_tokudb; # Print MariaDB Tokudb stats +mariadb_xtradb; # Print MariaDB XtraDB stats #mariadb_rockdb; # Print MariaDB RockDB stats #mariadb_spider; # Print MariaDB Spider stats @@ -6867,7 +7454,7 @@ __END__ =head1 NAME - MySQLTuner 1.9.9 - MySQL High Performance Tuning Script + MySQLTuner 2.6.1 - MySQL High Performance Tuning Script =head1 IMPORTANT USAGE GUIDELINES @@ -6890,7 +7477,8 @@ You must provide the remote server's total memory when connecting to other serve --mysqladmin Path to a custom mysqladmin executable --mysqlcmd Path to a custom mysql executable --defaults-file Path to a custom .my.cnf - --server-log Path to explict log file (error_log) + --defaults-extra-file Path to an extra custom config file + --server-log Path to explicit log file (error_log) =head1 PERFORMANCE AND REPORTING OPTIONS @@ -6898,45 +7486,53 @@ You must provide the remote server's total memory when connecting to other serve (Recommended for servers with many tables) --json Print result as JSON string --prettyjson Print result as JSON formatted string - --skippassword Don't perform checks on user passwords(default: off) + --skippassword Don't perform checks on user passwords (default: off) --checkversion Check for updates to MySQLTuner (default: don't check) --updateversion Check for updates to MySQLTuner and update when newer version is available (default: don't check) --forcemem Amount of RAM installed in megabytes --forceswap Amount of swap memory configured in megabytes - --passwordfile Path to a password file list(one password by line) + --passwordfile Path to a password file list (one password by line) --cvefile CVE File for vulnerability checks --outputfile Path to a output txt file --reportfile Path to a report txt file --template Path to a template file + --dumpdir Path to a directory where to dump information files + --feature Run a specific feature (see FEATURES section) + --dumpdir information_schema tables and sys views are dumped in CSV in this path =head1 OUTPUT OPTIONS --silent Don't output anything on screen - --verbose Prints out all options (default: no verbose, dbstat, idxstat, sysstat, tbstat, pfstat) + --verbose Print out all options (default: no verbose, dbstat, idxstat, sysstat, tbstat, pfstat) + --color Print output in color --nocolor Don't print output in color + --noprettyicon Print output with legacy tag [OK], [!!], [--], [CMD], ... --nogood Remove OK responses --nobad Remove negative/suggestion responses --noinfo Remove informational responses --debug Print debug information + --experimental Print experimental analysis (may fail) + --nondedicated Consider server is not dedicated to Db server usage only --noprocess Consider no other process is running --dbstat Print database information - --nodbstat Don't Print database information + --nodbstat Don't print database information --tbstat Print table information - --notbstat Don't Print table information + --notbstat Don't print table information --colstat Print column information - --nocolstat Don't Print column information + --nocolstat Don't print column information --idxstat Print index information - --noidxstat Don't Print index information + --noidxstat Don't print index information + --nomyisamstat Don't print MyIsam information --sysstat Print system information - --nosysstat Don't Print system information + --nosysstat Don't print system information + --nostructstat Don't print table structures information --pfstat Print Performance schema - --nopfstat Don't Print Performance schema - --bannedports Ports banned separated by comma(,) - --server-log Define specifi error_log to analyze - --maxportallowed Number of ports opened allowed on this hosts + --nopfstat Don't print Performance schema + --bannedports Ports banned separated by comma (,) + --server-log Define specific error_log to analyze + --maxportallowed Number of open ports allowable on this host --buffers Print global and per-thread buffer values - =head1 PERLDOC You can find documentation for this module with the perldoc command. @@ -6952,6 +7548,7 @@ L =head1 AUTHORS Major Hayden - major@mhtx.net +Jean-Marie Renouard - jmrenouard@gmail.com =head1 CONTRIBUTORS @@ -7093,6 +7690,10 @@ Stephan GroBberndt Christian Loos +=item * + +Long Radix + =back =head1 SUPPORT @@ -7102,7 +7703,7 @@ Bug reports, feature requests, and downloads at http://mysqltuner.pl/ Bug tracker can be found at https://github.com/major/MySQLTuner-perl/issues -Maintained by Major Hayden (major\@mhtx.net) - Licensed under GPL +Maintained by Jean-Marie Renouard (jmrenouard\@gmail.com) - Licensed under GPL =head1 SOURCE CODE @@ -7112,7 +7713,8 @@ L =head1 COPYRIGHT AND LICENSE -Copyright (C) 2006-2022 Major Hayden - major@mhtx.net +Copyright (C) 2006-2023 Major Hayden - major@mhtx.net +# Copyright (C) 2015-2023 Jean-Marie Renouard - jmrenouard@gmail.com For the latest updates, please visit http://mysqltuner.pl/ @@ -7138,4 +7740,4 @@ along with this program. If not, see . # indent-tabs-mode: t # cperl-indent-level: 8 # perl-indent-level: 8 -# End: +# End: \ No newline at end of file From 168db9abcd9ae20228efb2c658e5c93cc378a3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 18 Dec 2024 10:57:39 +0100 Subject: [PATCH 26/41] Refs #8140: MariaDB Server Deploy - Role WIP - mysql-flush.sh added for snapshots with QEMU Guest Agent. --- roles/services/defaults/main.yaml | 5 +++ roles/services/files/mysql-flush.sh | 57 +++++++++++++++++++++++++++++ roles/services/tasks/mariadb.yml | 5 +-- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 roles/services/files/mysql-flush.sh diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index d33e66b..6c51a67 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -5,6 +5,10 @@ mariadb_base_packages: mariadb_requeriments: - curl - apt-transport-https +certificates: + - { content: '{{ ca_mysql }}', dest: '/etc/mysql/ca.pem', mode: 'u=rw,g=r,o=r' } + - { content: '{{ cert_mysql }}', dest: '/etc/mysql/cert.pem', mode: 'u=rw,g=r,o=r' } + - { content: '{{ private_mysql }}', dest: '/etc/mysql/key.pem', mode: 'u=rw,g=,o=' } required_directories: - { path: /mnt/local-backup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } @@ -14,6 +18,7 @@ required_directories: - { path: /root/mariabackup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } required_files_and_mariabackup_files_and_root_scripts: - { src: "mariadb_override.conf", dest: "/etc/systemd/system/mariadb.service.d/override.conf", mode: "u=rw,g=r,o=r" } + - { src: "mysql-flush.sh", dest: "/etc/qemu/fsfreeze-hook.d/mysql-flush.sh", mode: "u=rwx,g=rx,o=rx" } - { src: "files/mariabackup/bacula-before.sh", dest: "/root/mariabackup/bacula-before.sh", mode: "u=rwx,g=rx,o=rx" } - { src: "files/mariabackup/config.sh", dest: "/root/mariabackup/config.sh", mode: "u=rwx,g=rx,o=x" } - { src: "files/mariabackup/inc-backup.sh", dest: "/root/mariabackup/inc-backup.sh", mode: "u=rwx,g=rx,o=rx" } diff --git a/roles/services/files/mysql-flush.sh b/roles/services/files/mysql-flush.sh new file mode 100644 index 0000000..88af1b8 --- /dev/null +++ b/roles/services/files/mysql-flush.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +# https://github.com/qemu/qemu/blob/master/scripts/qemu-guest-agent/fsfreeze-hook.d/mysql-flush.sh.sample +# Flush MySQL tables to the disk before the filesystem is frozen. +# At the same time, this keeps a read lock in order to avoid write accesses +# from the other clients until the filesystem is thawed. + +MYSQL="/usr/bin/mysql" +MYSQL_OPTS="-uroot" #"-prootpassword" +FIFO=/var/run/mysql-flush.fifo + +# Check mysql is installed and the server running +[ -x "$MYSQL" ] && "$MYSQL" $MYSQL_OPTS < /dev/null || exit 0 + +flush_and_wait() { + printf "FLUSH TABLES WITH READ LOCK \\G\n" + trap 'printf "$(date): $0 is killed\n">&2' HUP INT QUIT ALRM TERM + read < $FIFO + printf "UNLOCK TABLES \\G\n" + rm -f $FIFO +} + +case "$1" in + freeze) + mkfifo $FIFO || exit 1 + flush_and_wait | "$MYSQL" $MYSQL_OPTS & + # wait until every block is flushed + while [ "$(echo 'SHOW STATUS LIKE "Key_blocks_not_flushed"' |\ + "$MYSQL" $MYSQL_OPTS | tail -1 | cut -f 2)" -gt 0 ]; do + sleep 1 + done + # for InnoDB, wait until every log is flushed + INNODB_STATUS=$(mktemp /tmp/mysql-flush.XXXXXX) + [ $? -ne 0 ] && exit 2 + trap "rm -f $INNODB_STATUS; exit 1" HUP INT QUIT ALRM TERM + while :; do + printf "SHOW ENGINE INNODB STATUS \\G" |\ + "$MYSQL" $MYSQL_OPTS > $INNODB_STATUS + LOG_CURRENT=$(grep 'Log sequence number' $INNODB_STATUS |\ + tr -s ' ' | cut -d' ' -f4) + LOG_FLUSHED=$(grep 'Log flushed up to' $INNODB_STATUS |\ + tr -s ' ' | cut -d' ' -f5) + [ "$LOG_CURRENT" = "$LOG_FLUSHED" ] && break + sleep 1 + done + rm -f $INNODB_STATUS + ;; + + thaw) + [ ! -p $FIFO ] && exit 1 + echo > $FIFO + ;; + + *) + exit 1 + ;; +esac \ No newline at end of file diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index eb923e7..64c4198 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -78,10 +78,7 @@ owner: mysql group: mysql mode: "{{ item.mode }}" - loop: - - { content: '{{ ca_mysql }}', dest: '/etc/mysql/ca.pem', mode: 'u=rw,g=r,o=r' } - - { content: '{{ cert_mysql }}', dest: '/etc/mysql/cert.pem', mode: 'u=rw,g=r,o=r' } - - { content: '{{ private_mysql }}', dest: '/etc/mysql/key.pem', mode: 'u=rw,g=,o=' } + loop: "{{ certificates }}" notify: restart-mariadb - name: Set MariaDB custom configuration From a0870b248ad201136a7603477551c2949636fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 18 Dec 2024 11:33:30 +0100 Subject: [PATCH 27/41] Refs #8140: MariaDB Server Deploy - Role WIP - Private mysql cert to passbolt search. --- roles/services/defaults/main.yaml | 1 - roles/services/tasks/mariadb.yml | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/roles/services/defaults/main.yaml b/roles/services/defaults/main.yaml index 6c51a67..ef1a3d9 100644 --- a/roles/services/defaults/main.yaml +++ b/roles/services/defaults/main.yaml @@ -8,7 +8,6 @@ mariadb_requeriments: certificates: - { content: '{{ ca_mysql }}', dest: '/etc/mysql/ca.pem', mode: 'u=rw,g=r,o=r' } - { content: '{{ cert_mysql }}', dest: '/etc/mysql/cert.pem', mode: 'u=rw,g=r,o=r' } - - { content: '{{ private_mysql }}', dest: '/etc/mysql/key.pem', mode: 'u=rw,g=,o=' } required_directories: - { path: /mnt/local-backup, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } - { path: /mnt/mysqlbin, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' } diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index 64c4198..d56dd94 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,5 +1,4 @@ # Revisar /root/scripts/check-memory.sh --> No es óptimo hacer lo que hace ese programa -# Ver como lanzar el mysqltuner.pl # Revisar la tarea del cron tambien /root/scripts/scheduler-log.sh - name: Ensure Install requirements for MariaDB repository setup script @@ -81,6 +80,14 @@ loop: "{{ certificates }}" notify: restart-mariadb +- name: Configure MySQL master cert + copy: + content: "{{ lookup(passbolt, 'private_mysql', folder_parent_id=passbolt_folder).description }}" + dest: /etc/mysql/key.pem + owner: mysql + group: mysql + mode: u=rw,g=,o= + - name: Set MariaDB custom configuration copy: src: "{{ item }}" From 7d4843d8d0ca3ee729b83e46915604b0ef46fa58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Wed, 18 Dec 2024 16:05:29 +0100 Subject: [PATCH 28/41] Refs #8140: MariaDB Server Deploy - Role WIP - Some comments. --- roles/services/tasks/mariadb.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/roles/services/tasks/mariadb.yml b/roles/services/tasks/mariadb.yml index d56dd94..f9ec533 100644 --- a/roles/services/tasks/mariadb.yml +++ b/roles/services/tasks/mariadb.yml @@ -1,5 +1,5 @@ -# Revisar /root/scripts/check-memory.sh --> No es óptimo hacer lo que hace ese programa -# Revisar la tarea del cron tambien /root/scripts/scheduler-log.sh +# Review /root/scripts/check-memory.sh --> It's not optimal to do what this program does +# Also review the cron task /root/scripts/scheduler-log.sh - name: Ensure Install requirements for MariaDB repository setup script apt: @@ -97,4 +97,13 @@ mode: u=rw,g=r,o=r with_fileglob: - "files/z9*.cnf" - notify: restart-mariadb \ No newline at end of file + notify: restart-mariadb + +- name: Reminder to check mount points + debug: + msg: | + Remember to check the following mount points: + - /var/lib/mysql + - /mnt/mysqlbin + - /mnt/local-backup + Make sure they are correctly configured and accessible. From 208e6d2a549ecce7450188c98005cb612a588b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Lle=C3=B3=20Tom=C3=A1s?= Date: Fri, 20 Dec 2024 13:29:56 +0100 Subject: [PATCH 29/41] Refs #8142: Samba Server Deploy - Role WIP - Initial approach --- playbooks/delete.yml | 24 +++++++++++++++ roles/services/defaults/main.yaml | 9 ++++++ roles/services/tasks/adsamba.yml | 49 +++++++++++++++++++++++++++++++ roles/services/tasks/main.yml | 2 ++ 4 files changed, 84 insertions(+) create mode 100644 playbooks/delete.yml create mode 100644 roles/services/tasks/adsamba.yml diff --git a/playbooks/delete.yml b/playbooks/delete.yml new file mode 100644 index 0000000..b4e8215 --- /dev/null +++ b/playbooks/delete.yml @@ -0,0 +1,24 @@ +- name: List all disks + hosts: all + tasks: + - name: Get info disk information 2 + shell: blkid | grep LABEL | awk {'print $2'} + register: blkid + + - name: Print valid labels + debug: + var: blkid + + - name: Parsear stdout_lines para buscar etiquetas específicas + set_fact: + found_labels: >- + {{ + blkid.stdout_lines + | map('regex_search', 'LABEL="(?P