diff --git a/mysqltuner.pl b/mysqltuner.pl index 474a2a2..99cc21d 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -41,7 +41,7 @@ use File::Spec; use Getopt::Long; use File::Basename; use Cwd 'abs_path'; -#use Data::Dumper qw/Dumper/; +use Data::Dumper qw/Dumper/; # Set up a few variables for use in the script my $tunerversion = "1.4.6"; my (@adjvars, @generalrec); @@ -67,6 +67,7 @@ my %opt = ( "reportfile" => 0, "dbstat" => 0, "idxstat" => 0, + "skippassword" => 0, ); @@ -90,6 +91,7 @@ GetOptions(\%opt, 'mysqlcmd=s', 'help', 'buffers', + 'skippassword', 'passwordfile=s', 'reportfile=s', 'silent', @@ -123,6 +125,7 @@ sub usage { " Performance and Reporting Options\n". " --skipsize Don't enumerate tables and their types/sizes (default: on)\n". " (Recommended for servers with many tables)\n". + " --skippassword Don't perform checks on user passwords(default: off)\n". " --checkversion Check for updates to MySQLTuner (default: don't check)\n". " --forcemem Amount of RAM installed in megabytes\n". " --forceswap Amount of swap memory configured in megabytes\n". @@ -480,16 +483,20 @@ sub select_one { } # Populates all of the variable and status hashes -my (%mystat,%myvar,$dummyselect); +my (%mystat,%myvar,$dummyselect,%myrepl, %myslaves); sub get_all_vars { # We need to initiate at least one query so that our data is useable - $dummyselect = `$mysqlcmd $mysqllogin -Bse "SELECT VERSION();"`; - my @mysqlvarlist = `$mysqlcmd $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ VARIABLES;"`; + $dummyselect = select_one "SELECT VERSION()"; + debugprint "VERSION: ".$dummyselect."\n"; + + my @mysqlvarlist = select_array "SHOW /*!50000 GLOBAL */ VARIABLES"; foreach my $line (@mysqlvarlist) { $line =~ /([a-zA-Z_]*)\s*(.*)/; $myvar{$1} = $2; + debugprint "$1 = $2\n"; } - my @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ STATUS;"`; + + my @mysqlstatlist = select_array "SHOW /*!50000 GLOBAL */ STATUS"; foreach my $line (@mysqlstatlist) { $line =~ /([a-zA-Z_]*)\s*(.*)/; $mystat{$1} = $2; @@ -501,7 +508,7 @@ sub get_all_vars { # 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 = `$mysqlcmd $mysqllogin -Bse "SHOW ENGINES;" 2>$devnull`; + my @mysqlenginelist = select_array "SHOW ENGINES"; foreach my $line (@mysqlenginelist) { if ($line =~ /^([a-zA-Z_]+)\s+(\S+)/) { my $engine = lc($1); @@ -514,6 +521,24 @@ sub get_all_vars { $myvar{"have_$engine"} = $val; } } + + my @mysqlslave = select_array "SHOW SLAVE STATUS\\G"; + + foreach my $line (@mysqlslave) { + if ($line =~ /\s*(.*):\s*(.*)/) { + debugprint "$1 => $2\n"; + $myrepl{"$1"} = $2; + } + } + #print Dumper(%myrepl); + #exit 0; + my @mysqlslaves = select_array "SHOW SLAVE HOSTS"; + my @lineitems=(); + foreach my $line (@mysqlslaves) { + debugprint "L: $line \n"; + @lineitems=split /\s*/, $line; + $myslaves{$lineitems[0]}=$line; + } } sub get_basic_passwords { @@ -526,9 +551,12 @@ sub get_basic_passwords { sub security_recommendations { prettyprint "\n-------- Security Recommendations -------------------------------------------\n"; - + if ($opt{skippassword} eq 1) { + infoprint "Skipped due to --skippassword option\n"; + return; + } # Looking for Anonymous users - my @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL ;"`; + my @mysqlstatlist = select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL"; if (@mysqlstatlist) { foreach my $line (sort @mysqlstatlist) { chomp($line); @@ -540,7 +568,7 @@ sub security_recommendations { } # Looking for Empty Password - @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = '' OR password IS NULL;"`; + @mysqlstatlist = select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = '' OR password IS NULL"; if (@mysqlstatlist) { foreach my $line (sort @mysqlstatlist) { chomp($line); @@ -552,7 +580,7 @@ sub security_recommendations { } # Looking for User with user/ uppercase /capitalise user as password - @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE CAST(password as Binary) = PASSWORD(user) OR CAST(password as Binary) = PASSWORD(UPPER(user)) OR CAST(password as Binary) = PASSWORD(UPPER(LEFT(User, 1)) + SUBSTRING(User, 2, LENGTH(User)));"`; + @mysqlstatlist = select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE CAST(password as Binary) = PASSWORD(user) OR CAST(password as Binary) = PASSWORD(UPPER(user)) OR CAST(password as Binary) = PASSWORD(UPPER(LEFT(User, 1)) + SUBSTRING(User, 2, LENGTH(User)))"; if (@mysqlstatlist) { foreach my $line (sort @mysqlstatlist) { chomp($line); @@ -561,7 +589,7 @@ sub security_recommendations { push(@generalrec, "Set up a Secure Password for user\@host ( SET PASSWORD FOR 'user'\@'SpecificDNSorIp' = PASSWORD('secure_password'); )"); } - @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE HOST='%';"`; + @mysqlstatlist = select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE HOST='%'"; if (@mysqlstatlist) { foreach my $line (sort @mysqlstatlist) { chomp($line); @@ -584,9 +612,8 @@ sub security_recommendations { $pass=~s/\s//g; chomp($pass); # Looking for User with user/ uppercase /capitalise weak password - $passreq="SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = PASSWORD('".$pass."') OR password = PASSWORD(UPPER('".$pass."')) OR password = PASSWORD(UPPER(LEFT('".$pass."', 1)) + SUBSTRING('".$pass."', 2, LENGTH('".$pass."')));\n"; - @mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "$passreq"`; - #infoprint "There is ".scalar (@mysqlstatlist). " items.\n"; + @mysqlstatlist = select_array "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = PASSWORD('".$pass."') OR password = PASSWORD(UPPER('".$pass."')) OR password = PASSWORD(UPPER(LEFT('".$pass."', 1)) + SUBSTRING('".$pass."', 2, LENGTH('".$pass."')))"; + debugprint "There is ".scalar (@mysqlstatlist). " items.\n"; if (@mysqlstatlist) { foreach my $line (@mysqlstatlist) { chomp($line); @@ -604,16 +631,30 @@ sub security_recommendations { sub get_replication_status { prettyprint "\n-------- Replication Metrics -------------------------------------------------\n"; - my $slave_status = `$mysqlcmd $mysqllogin -Bse "show slave status\\G"`; - if( $slave_status eq '' ) { - infoprint "No replication setup for this server.\n"; + if( scalar(keys %myslaves)==0 ) { + infoprint "No replication slave(s) for this server.\n"; + } else { + infoprint "This server is acting as master for ".scalar(keys %myslaves)." server(s).\n"; + } + + if( scalar(keys %myrepl)==0 and scalar(keys %myslaves)==0 ) { + infoprint "This is a standalone server..\n"; return; } - debugprint "$slave_status \n"; - my ($io_running) = ($slave_status =~ /slave_io_running\S*\s+(\S+)/i); - my ($sql_running) = ($slave_status =~ /slave_sql_running\S*\s+(\S+)/i); - my ($seconds_behind_master) = ($slave_status =~ /seconds_behind_master\S*\s+(\S+)/i); - if ($io_running eq 'Yes' && $sql_running eq 'Yes') { + if( scalar(keys %myrepl)==0 ) { + infoprint "No replication setup for this server.\n"; + } + my ($io_running) = $myrepl{'Slave_IO_Running'}; + debugprint "IO RUNNING: $io_running \n"; + my ($sql_running) = $myrepl{'Slave_SQL_Running'}; + debugprint "SQL RUNNING: $sql_running \n"; + my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'}; + debugprint "SECONDS : $seconds_behind_master \n"; + + 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 configurated."; + } + 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 { @@ -695,7 +736,7 @@ sub check_storage_engines { my $engines; if (mysql_version_ge(5, 1)) { - my @engineresults = `$mysqlcmd $mysqllogin -Bse "SELECT ENGINE,SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('performance_schema','MyISAM','MERGE','MEMORY') ORDER BY ENGINE ASC"`; + 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]+)/; @@ -712,7 +753,7 @@ sub check_storage_engines { infoprint "Status: $engines\n"; if (mysql_version_ge(5)) { # MySQL 5 servers can have table sizes calculated quickly from information schema - my @templist = `$mysqlcmd $mysqllogin -Bse "SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE) 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 @templist = select_array "SELECT ENGINE,SUM(DATA_LENGTH+INDEX_LENGTH),COUNT(ENGINE) 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;"; foreach my $line (@templist) { my ($engine,$size,$count); @@ -721,13 +762,13 @@ sub check_storage_engines { $enginestats{$engine} = $size; $enginecount{$engine} = $count; } - $fragtables = `$mysqlcmd $mysqllogin -Bse "SELECT COUNT(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema','performance_schema', 'mysql') AND Data_free > 0 AND NOT ENGINE='MEMORY';"`; + $fragtables = select_one "SELECT COUNT(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema','performance_schema', 'mysql') AND Data_free > 0 AND NOT ENGINE='MEMORY'"; chomp($fragtables); } 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 - my @dblist = `$mysqlcmd $mysqllogin -Bse "SHOW DATABASES"`; + my @dblist = select_array "SHOW DATABASES"; foreach my $db (@dblist) { chomp($db); if ($db eq "information_schema" or $db eq "performance_schema" or $db eq "mysql") { next; } @@ -736,7 +777,7 @@ sub check_storage_engines { # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column @ixs = (1, 5, 8); } - push(@tblist, map { [ (split)[@ixs] ] } `$mysqlcmd $mysqllogin -Bse "SHOW TABLE STATUS FROM \\\`$db\\\`"`); + 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; @@ -782,10 +823,10 @@ sub check_storage_engines { # Auto increments my %tblist; # Find the maximum integer - my $maxint = `$mysqlcmd $mysqllogin -Bse "SELECT ~0"`; + my $maxint = select_one "SELECT ~0"; # Now we build a database list, and loop through it to get storage engine stats for tables - my @dblist = `$mysqlcmd $mysqllogin -Bse "SHOW DATABASES"`; + my @dblist = select_array "SHOW DATABASES"; foreach my $db (@dblist) { chomp($db); @@ -800,7 +841,7 @@ sub check_storage_engines { # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column @ia = (0, 9); } - push(@{$tblist{$db}}, map { [ (split)[@ia] ] } `$mysqlcmd $mysqllogin -Bse "SHOW TABLE STATUS FROM \\\`$db\\\`"`); + push(@{$tblist{$db}}, map { [ (split)[@ia] ] } select_array "SHOW TABLE STATUS FROM \\\`$db\\\`"); } my @dbnames = keys %tblist; @@ -901,7 +942,7 @@ sub calculations { $size += (split)[0] for `find $myvar{'datadir'} -name "*.MYI" 2>&1 | xargs du -L $duflags 2>&1`; $mycalc{'total_myisam_indexes'} = $size; } elsif (mysql_version_ge(5)) { - $mycalc{'total_myisam_indexes'} = `$mysqlcmd $mysqllogin -Bse "SELECT IFNULL(SUM(INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';"`; + $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';"; } if (defined $mycalc{'total_myisam_indexes'} and $mycalc{'total_myisam_indexes'} == 0) { $mycalc{'total_myisam_indexes'} = "fail"; @@ -986,12 +1027,12 @@ sub calculations { $mycalc{'innodb_log_size_pct'} = ($myvar{'innodb_log_file_size'} * 100 / $myvar{'innodb_buffer_pool_size'}); } ($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_reads'}/$mystat{'Innodb_buffer_pool_read_requests'}) if defined $mystat{'Innodb_buffer_pool_read_requests'}; + $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'}."\n"; debugprint "Innodb_buffer_pool_reads: ".$mystat{'Innodb_buffer_pool_reads'}."\n"; debugprint "Innodb_buffer_pool_read_requests: ".$mystat{'Innodb_buffer_pool_read_requests'}."\n"; - ($mystat{'Innodb_buffer_pool_write_requests'}, $mystat{'Innodb_buffer_pool_writes'})=(1,1) unless defined $mystat{'Innodb_buffer_pool_writes'}; - $mycalc{'pct_write_efficiency'}=percentage($mystat{'Innodb_buffer_pool_writes'}/$mystat{'Innodb_buffer_pool_write_requests'}) if defined $mystat{'Innodb_buffer_pool_write_requests'}; + ($mystat{'Innodb_buffer_pool_write_requests'}, $mystat{'Innodb_buffer_pool_writes'})=(1,1) unless defined $mystat{'Innodb_buffer_pool_writes'}; + $mycalc{'pct_write_efficiency'}=percentage(($mystat{'Innodb_buffer_pool_write_requests'}-$mystat{'Innodb_buffer_pool_writes'}), $mystat{'Innodb_buffer_pool_write_requests'}) if defined $mystat{'Innodb_buffer_pool_write_requests'}; debugprint "pct_write_efficiency: ".$mycalc{'pct_read_efficiency'}."\n"; debugprint "Innodb_buffer_pool_writes: ".$mystat{'Innodb_buffer_pool_writes'}."\n"; debugprint "Innodb_buffer_pool_write_requests: ".$mystat{'Innodb_buffer_pool_write_requests'}."\n"; @@ -1356,16 +1397,16 @@ sub mysql_innodb { # InnoDB Read efficency if (defined $mycalc{'pct_read_efficiency'} && $mycalc{'pct_read_efficiency'} < 90 ) { - badprint "InnoDB Read buffer efficiency: ".$mycalc{'pct_read_efficiency'}. "% (".$mystat{'Innodb_buffer_pool_reads'}." hits/ ".$mystat{'Innodb_buffer_pool_read_requests'}." total)\n"; + 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)\n"; } else { - goodprint "InnoDB Read buffer efficiency: ".$mycalc{'pct_read_efficiency'}. "% (".$mystat{'Innodb_buffer_pool_reads'}." hits/ ".$mystat{'Innodb_buffer_pool_read_requests'}." total)\n"; + 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)\n"; } # InnoDB Write efficency if (defined $mycalc{'pct_write_efficiency'} && $mycalc{'pct_write_efficiency'} < 90 ) { - badprint "InnoDB Write buffer efficiency: ".$mycalc{'pct_write_efficiency'}. "% (".$mystat{'Innodb_buffer_pool_writes'}." hits/ ".$mystat{'Innodb_buffer_pool_write_requests'}." total)\n"; + badprint "InnoDB Write buffer efficiency: ".$mycalc{'pct_write_efficiency'}. "% (".($mystat{'Innodb_buffer_pool_write_requests'} - $mystat{'Innodb_buffer_pool_writes'})." hits/ ".$mystat{'Innodb_buffer_pool_write_requests'}." total)\n"; } else { - goodprint "InnoDB Write buffer efficiency: ".$mycalc{'pct_write_efficiency'}. "% (".$mystat{'Innodb_buffer_pool_writes'}." hits/ ".$mystat{'Innodb_buffer_pool_write_requests'}." total)\n"; + goodprint "InnoDB Write buffer efficiency: ".$mycalc{'pct_write_efficiency'}. "% (".($mystat{'Innodb_buffer_pool_write_requests'} - $mystat{'Innodb_buffer_pool_writes'})." hits/ ".$mystat{'Innodb_buffer_pool_write_requests'}." total)\n"; } # InnoDB Log Waits @@ -1570,6 +1611,7 @@ You must provide the remote server's total memory when connecting to other serve --skipsize Don't enumerate tables and their types/sizes (default: on) (Recommended for servers with many tables) + --skippassword Don't perform checks on user passwords(default: off) --checkversion Check for updates to MySQLTuner (default: don't check) --forcemem Amount of RAM installed in megabytes --forceswap Amount of swap memory configured in megabytes