2008-09-08 01:16:39 +00:00
#!/usr/bin/perl -w
2015-06-16 21:38:17 +00:00
# mysqltuner.pl - Version 1.4.4
2008-09-08 01:16:39 +00:00
# High Performance MySQL Tuning Script
2014-02-22 01:43:08 +00:00
# Copyright (C) 2006-2014 Major Hayden - major@mhtx.net
2008-09-08 01:16:39 +00:00
#
# For the latest updates, please visit http://mysqltuner.com/
2014-02-22 01:43:08 +00:00
# Git repository available at http://github.com/major/MySQLTuner-perl
2008-09-08 01:16:39 +00:00
#
# 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 <http://www.gnu.org/licenses/>.
#
# This project would not be possible without help from:
2009-09-17 23:24:33 +00:00
# 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
2014-12-04 15:56:31 +00:00
# Baptiste M. Cole Turner
2008-09-08 01:16:39 +00:00
#
# Inspired by Matthew Montgomery's tuning-primer.sh script:
# http://forge.mysql.com/projects/view.php?id=44
#
use strict ;
use warnings ;
use diagnostics ;
2011-04-02 12:03:32 +00:00
use File::Spec ;
2008-09-08 01:16:39 +00:00
use Getopt::Long ;
2015-06-12 14:09:59 +00:00
use File::Basename ;
use Cwd 'abs_path' ;
2015-06-18 19:48:03 +00:00
#use Data::Dumper qw/Dumper/;
2008-09-08 01:16:39 +00:00
# Set up a few variables for use in the script
2015-06-16 21:38:17 +00:00
my $ tunerversion = "1.4.4" ;
2008-09-08 01:16:39 +00:00
my ( @ adjvars , @ generalrec ) ;
# Set defaults
my % opt = (
2009-09-17 23:24:33 +00:00
"nobad" = > 0 ,
"nogood" = > 0 ,
"noinfo" = > 0 ,
"nocolor" = > 0 ,
"forcemem" = > 0 ,
2014-12-04 15:56:31 +00:00
"forceswap" = > 0 ,
2009-09-17 23:24:33 +00:00
"host" = > 0 ,
"socket" = > 0 ,
"port" = > 0 ,
"user" = > 0 ,
"pass" = > 0 ,
"skipsize" = > 0 ,
2014-12-04 15:56:31 +00:00
"checkversion" = > 0 ,
2014-10-23 10:37:11 +00:00
"buffers" = > 0 ,
2015-06-15 13:23:05 +00:00
"passwordfile" = > 0 ,
2015-06-16 21:38:17 +00:00
"reportfile" = > 0 ,
2008-09-08 01:16:39 +00:00
) ;
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Gather the options from the command line
GetOptions ( \ % opt ,
'nobad' ,
'nogood' ,
'noinfo' ,
'nocolor' ,
'forcemem=i' ,
'forceswap=i' ,
'host=s' ,
'socket=s' ,
'port=i' ,
'user=s' ,
'pass=s' ,
'skipsize' ,
'checkversion' ,
2013-03-19 09:56:01 +00:00
'mysqladmin=s' ,
2015-04-16 14:58:48 +00:00
'mysqlcmd=s' ,
2008-09-08 01:16:39 +00:00
'help' ,
2014-10-23 10:37:11 +00:00
'buffers' ,
2015-06-15 13:23:05 +00:00
'passwordfile=s' ,
2015-06-16 21:38:17 +00:00
'reportfile=s' ,
'silent' ,
2008-09-08 01:16:39 +00:00
) ;
if ( defined $ opt { 'help' } && $ opt { 'help' } == 1 ) { usage ( ) ; }
sub usage {
# Shown with --help option passed
print "\n" .
" MySQLTuner $tunerversion - MySQL High Performance Tuning Script\n" .
" Bug reports, feature requests, and downloads at http://mysqltuner.com/\n" .
" Maintained by Major Hayden (major\@mhtx.net) - Licensed under GPL\n" .
"\n" .
" Important Usage Guidelines:\n" .
" To run the script with the default options, run the script without arguments\n" .
" Allow MySQL server to run for at least 24-48 hours before trusting suggestions\n" .
" Some routines may require root level privileges (script will provide warnings)\n" .
" You must provide the remote server's total memory when connecting to other servers\n" .
"\n" .
" Connection and Authentication\n" .
" --host <hostname> Connect to a remote host to perform tests (default: localhost)\n" .
" --socket <socket> Use a different socket for a local connection\n" .
" --port <port> Port to use for connection (default: 3306)\n" .
" --user <username> Username to use for authentication\n" .
" --pass <password> Password to use for authentication\n" .
2014-02-21 17:32:49 +00:00
" --mysqladmin <path> Path to a custom mysqladmin executable\n" .
2015-04-16 14:58:48 +00:00
" --mysqlcmd <path> Path to a custom mysql executable\n" .
2008-09-08 01:16:39 +00:00
"\n" .
" Performance and Reporting Options\n" .
" --skipsize Don't enumerate tables and their types/sizes (default: on)\n" .
" (Recommended for servers with many tables)\n" .
" --checkversion Check for updates to MySQLTuner (default: don't check)\n" .
" --forcemem <size> Amount of RAM installed in megabytes\n" .
" --forceswap <size> Amount of swap memory configured in megabytes\n" .
2015-06-15 13:23:05 +00:00
" --passwordfile <path>Path to a password file list(one password by line)\n" .
2015-06-16 21:38:17 +00:00
" --reportfile <path> Path to a report txt file\n" .
2008-09-08 01:16:39 +00:00
"\n" .
" Output Options:\n" .
" --nogood Remove OK responses\n" .
" --nobad Remove negative/suggestion responses\n" .
" --noinfo Remove informational responses\n" .
" --nocolor Don't print output in color\n" .
2014-10-23 10:37:11 +00:00
" --buffers Print global and per-thread buffer values\n" .
2008-09-08 01:16:39 +00:00
"\n" ;
exit ;
}
2011-04-02 12:03:32 +00:00
my $ devnull = File::Spec - > devnull ( ) ;
2015-06-15 13:23:05 +00:00
my $ basic_password_files = ( $ opt { passwordfile } eq "0" ) ? abs_path ( dirname ( __FILE__ ) ) . "/basic_passwords.txt" : abs_path ( $ opt { passwordfile } ) ;
2015-06-15 07:47:24 +00:00
2015-06-16 21:38:17 +00:00
#
my $ reportfile = undef ;
$ reportfile = abs_path ( $ opt { reportfile } ) unless $ opt { reportfile } eq "0" ;
my $ fh = undef ;
open ( $ fh , '>' , $ reportfile ) or die ( "Fail opening $reportfile" ) if defined ( $ reportfile ) ;
2015-06-17 15:50:06 +00:00
$ opt { nocolor } = 1 if defined ( $ reportfile ) ;
2015-06-16 21:38:17 +00:00
2008-09-08 01:16:39 +00:00
# Setting up the colors for the print styles
2009-05-28 02:16:16 +00:00
my $ good = ( $ opt { nocolor } == 0 ) ? "[\e[0;32mOK\e[0m]" : "[OK]" ;
2015-06-16 21:38:17 +00:00
my $ bad = ( $ opt { nocolor } == 0 ) ? "[\e[0;31m!!\e[0m]" : "[!!]" ;
2009-05-28 02:16:16 +00:00
my $ info = ( $ opt { nocolor } == 0 ) ? "[\e[0;34m--\e[0m]" : "[--]" ;
2008-09-08 01:16:39 +00:00
# Functions that handle the print styles
2015-06-16 21:38:17 +00:00
sub prettyprint {
print $ _ [ 0 ] ;
print $ fh $ _ [ 0 ] 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 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 ] ; }
2008-09-08 01:16:39 +00:00
# Calculates the parameter passed in bytes, and then rounds it to one decimal place
sub hr_bytes {
my $ num = shift ;
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" ;
}
}
# Calculates the parameter passed in bytes, and then rounds it to the nearest integer
sub hr_bytes_rnd {
my $ num = shift ;
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 ;
}
}
# 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 ) ;
sub os_setup {
sub memerror {
badprint "Unable to determine total memory/swap; use '--forcemem' and '--forceswap'\n" ;
exit ;
}
my $ os = `uname` ;
$ duflags = ( $ os =~ /Linux/ ) ? '-b' : '' ;
if ( $ opt { 'forcemem' } > 0 ) {
$ physical_memory = $ opt { 'forcemem' } * 1048576 ;
infoprint "Assuming $opt{'forcemem'} MB of physical memory\n" ;
if ( $ opt { 'forceswap' } > 0 ) {
$ swap_memory = $ opt { 'forceswap' } * 1048576 ;
infoprint "Assuming $opt{'forceswap'} MB of swap space\n" ;
} else {
$ swap_memory = 0 ;
badprint "Assuming 0 MB of swap space (use --forceswap to specify)\n" ;
}
} else {
if ( $ os =~ /Linux/ ) {
2015-06-16 12:26:08 +00:00
$ physical_memory = `LANG=en free -b | grep Mem | awk '{print \$2}'` or memerror ;
$ swap_memory = `LANG=en free -b | grep Swap | awk '{print \$2}'` or memerror ;
2008-09-08 01:16:39 +00:00
} elsif ( $ os =~ /Darwin/ ) {
$ physical_memory = `sysctl -n hw.memsize` or memerror ;
$ swap_memory = `sysctl -n vm.swapusage | awk '{print \$3}' | sed 's/\..*\$//'` or memerror ;
2012-12-21 11:51:28 +00:00
} elsif ( $ os =~ /NetBSD|OpenBSD|FreeBSD/ ) {
2008-09-08 01:16:39 +00:00
$ physical_memory = `sysctl -n hw.physmem` or memerror ;
2008-11-02 18:25:55 +00:00
if ( $ physical_memory < 0 ) {
$ physical_memory = `sysctl -n hw.physmem64` or memerror ;
}
2008-09-08 01:16:39 +00:00
$ swap_memory = `swapctl -l | grep '^/' | awk '{ s+= \$2 } END { print s }'` or memerror ;
} elsif ( $ os =~ /BSD/ ) {
2012-12-21 11:51:28 +00:00
$ physical_memory = `sysctl -n hw.realmem` or memerror ;
2008-09-08 01:16:39 +00:00
$ swap_memory = `swapinfo | grep '^/' | awk '{ s+= \$2 } END { print s }'` ;
2008-11-02 18:25:55 +00:00
} elsif ( $ os =~ /SunOS/ ) {
2009-05-28 02:16:16 +00:00
$ physical_memory = `/usr/sbin/prtconf | grep Memory | cut -f 3 -d ' '` or memerror ;
2008-11-02 18:25:55 +00:00
chomp ( $ physical_memory ) ;
$ physical_memory = $ physical_memory * 1024 * 1024 ;
2009-09-17 23:24:33 +00:00
} 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 ;
2008-09-08 01:16:39 +00:00
}
}
chomp ( $ physical_memory ) ;
}
# Checks to see if a MySQL login is possible
2015-04-16 14:58:48 +00:00
my ( $ mysqllogin , $ doremote , $ remotestring , $ mysqlcmd ) ;
2008-09-08 01:16:39 +00:00
sub mysql_setup {
$ doremote = 0 ;
$ remotestring = '' ;
2014-02-21 17:32:49 +00:00
my $ mysqladmincmd ;
if ( $ opt { mysqladmin } ) {
$ mysqladmincmd = $ opt { mysqladmin } ;
} else {
$ mysqladmincmd = `which mysqladmin` ;
}
chomp ( $ mysqladmincmd ) ;
if ( ! - e $ mysqladmincmd && $ opt { mysqladmin } ) {
badprint "Unable to find the mysqladmin command you specified: " . $ mysqladmincmd . "\n" ;
exit ;
} elsif ( ! - e $ mysqladmincmd ) {
badprint "Couldn't find mysqladmin in your \$PATH. Is MySQL installed?\n" ;
2008-09-08 01:16:39 +00:00
exit ;
}
2013-03-19 09:56:01 +00:00
2015-04-16 14:58:48 +00:00
if ( $ opt { mysqlcmd } ) {
$ mysqlcmd = $ opt { mysqlcmd } ;
} else {
$ mysqlcmd = `which mysql` ;
}
chomp ( $ mysqlcmd ) ;
if ( ! - e $ mysqlcmd && $ opt { mysqlcmd } ) {
badprint "Unable to find the mysql command you specified: " . $ mysqlcmd . "\n" ;
exit ;
} elsif ( ! - e $ mysqlcmd ) {
badprint "Couldn't find mysql in your \$PATH. Is MySQL installed?\n" ;
exit ;
}
2013-03-19 09:56:01 +00:00
2008-09-08 01:16:39 +00:00
# Are we being asked to connect via a socket?
if ( $ opt { socket } ne 0 ) {
$ remotestring = " -S $opt{socket}" ;
}
# 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 ) {
badprint "The --forcemem option is required for remote connections\n" ;
exit ;
}
infoprint "Performing tests on $opt{host}:$opt{port}\n" ;
$ remotestring = " -h $opt{host} -P $opt{port}" ;
$ doremote = 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 ;
2014-02-21 17:32:49 +00:00
my $ loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1` ;
2008-09-08 01:16:39 +00:00
if ( $ loginstatus =~ /mysqld is alive/ ) {
goodprint "Logged in using credentials passed on the command line\n" ;
return 1 ;
} else {
badprint "Attempted to use login credentials, but they were invalid\n" ;
exit 0 ;
}
}
2014-02-22 00:44:12 +00:00
my $ svcprop = `which svcprop 2>/dev/null` ;
if ( substr ( $ svcprop , 0 , 1 ) =~ "/" ) {
2013-02-12 22:53:51 +00:00
# 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.\n" ;
return 1 ;
} else {
badprint "Attempted to use login credentials from mysql-quickbackup, but they failed.\n" ;
exit 0 ;
}
}
} elsif ( - r "/etc/psa/.psa.shadow" and $ doremote == 0 ) {
2008-09-08 01:16:39 +00:00
# It's a Plesk box, use the available credentials
$ mysqllogin = "-u admin -p`cat /etc/psa/.psa.shadow`" ;
2014-02-21 17:32:49 +00:00
my $ loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1` ;
2008-09-08 01:16:39 +00:00
unless ( $ loginstatus =~ /mysqld is alive/ ) {
badprint "Attempted to use login credentials from Plesk, but they failed.\n" ;
exit 0 ;
}
2012-04-26 12:49:29 +00:00
} 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.\n" ;
exit 0 ;
}
2011-04-20 11:23:30 +00:00
} elsif ( - r "/etc/mysql/debian.cnf" and $ doremote == 0 ) {
2011-04-21 12:36:10 +00:00
# We have a debian maintenance account, use it
2011-04-21 06:32:29 +00:00
$ mysqllogin = "--defaults-file=/etc/mysql/debian.cnf" ;
2014-02-21 17:32:49 +00:00
my $ loginstatus = `$mysqladmincmd $mysqllogin ping 2>&1` ;
2011-04-21 12:36:10 +00:00
if ( $ loginstatus =~ /mysqld is alive/ ) {
goodprint "Logged in using credentials from debian maintenance account.\n" ;
return 1 ;
} else {
2011-04-20 11:23:30 +00:00
badprint "Attempted to use login credentials from debian maintenance account, but they failed.\n" ;
exit 0 ;
}
2008-09-08 01:16:39 +00:00
} else {
2011-04-21 12:36:10 +00:00
# It's not Plesk or debian, we should try a login
2014-02-21 17:32:49 +00:00
my $ loginstatus = `$mysqladmincmd $remotestring ping 2>&1` ;
2008-09-08 01:16:39 +00:00
if ( $ loginstatus =~ /mysqld is alive/ ) {
# Login went just fine
2008-11-02 18:25:55 +00:00
$ mysqllogin = " $remotestring " ;
2008-09-08 01:16:39 +00:00
# Did this go well because of a .my.cnf file or is there no password set?
2009-05-28 02:16:16 +00:00
my $ userpath = `printenv HOME` ;
2008-09-08 01:16:39 +00:00
if ( length ( $ userpath ) > 0 ) {
chomp ( $ userpath ) ;
}
2015-06-18 08:12:23 +00:00
unless ( - e "${userpath}/.my.cnf" or - e "${userpath}/.mylogin.cnf" ) {
2008-09-08 01:16:39 +00:00
badprint "Successfully authenticated with no password - SECURITY RISK!\n" ;
}
return 1 ;
} else {
print STDERR "Please enter your MySQL administrative login: " ;
my $ name = < > ;
print STDERR "Please enter your MySQL administrative password: " ;
2011-04-02 12:03:32 +00:00
system ( "stty -echo >$devnull 2>&1" ) ;
2008-09-08 01:16:39 +00:00
my $ password = < > ;
2011-04-02 12:03:32 +00:00
system ( "stty echo >$devnull 2>&1" ) ;
2008-09-08 01:16:39 +00:00
chomp ( $ password ) ;
chomp ( $ name ) ;
$ mysqllogin = "-u $name" ;
if ( length ( $ password ) > 0 ) {
$ mysqllogin . = " -p'$password'" ;
}
$ mysqllogin . = $ remotestring ;
2014-02-21 17:32:49 +00:00
my $ loginstatus = `$mysqladmincmd ping $mysqllogin 2>&1` ;
2008-09-08 01:16:39 +00:00
if ( $ loginstatus =~ /mysqld is alive/ ) {
print STDERR "\n" ;
if ( ! length ( $ password ) ) {
# Did this go well because of a .my.cnf file or is there no password set?
my $ userpath = `ls -d ~` ;
chomp ( $ userpath ) ;
unless ( - e "$userpath/.my.cnf" ) {
badprint "Successfully authenticated with no password - SECURITY RISK!\n" ;
}
}
return 1 ;
} else {
print "\n" . $ bad . " Attempted to use login credentials, but they were invalid.\n" ;
exit 0 ;
}
exit 0 ;
}
}
}
# Populates all of the variable and status hashes
my ( % mystat , % myvar , $ dummyselect ) ;
sub get_all_vars {
# We need to initiate at least one query so that our data is useable
2015-04-16 14:58:48 +00:00
$ dummyselect = `$mysqlcmd $mysqllogin -Bse "SELECT VERSION();"` ;
my @ mysqlvarlist = `$mysqlcmd $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ VARIABLES;"` ;
2008-09-08 01:16:39 +00:00
foreach my $ line ( @ mysqlvarlist ) {
$ line =~ /([a-zA-Z_]*)\s*(.*)/ ;
$ myvar { $ 1 } = $ 2 ;
}
2015-04-16 14:58:48 +00:00
my @ mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ STATUS;"` ;
2008-09-08 01:16:39 +00:00
foreach my $ line ( @ mysqlstatlist ) {
$ line =~ /([a-zA-Z_]*)\s*(.*)/ ;
$ mystat { $ 1 } = $ 2 ;
}
2011-03-06 11:04:08 +00:00
# Workaround for MySQL bug #59393 wrt. ignore-builtin-innodb
if ( ( $ myvar { 'ignore_builtin_innodb' } || "" ) eq "ON" ) {
$ myvar { 'have_innodb' } = "NO" ;
}
2011-03-06 10:59:46 +00:00
# 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
2015-04-16 14:58:48 +00:00
my @ mysqlenginelist = `$mysqlcmd $mysqllogin -Bse "SHOW ENGINES;" 2>$devnull` ;
2011-03-06 10:59:46 +00:00
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 ;
}
}
2008-09-08 01:16:39 +00:00
}
2015-06-12 14:09:59 +00:00
sub get_basic_passwords {
my $ file = shift ;
open ( FH , "< $file" ) or die "Can't open $file for read: $!" ;
my @ lines = <FH> ;
close FH or die "Cannot close $file: $!" ;
return @ lines
}
2009-09-17 23:13:22 +00:00
sub security_recommendations {
2015-06-16 21:38:17 +00:00
prettyprint "\n-------- Security Recommendations -------------------------------------------\n" ;
2015-06-12 14:09:59 +00:00
# Looking for Anonymous users
my @ mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE TRIM(USER) = '' OR USER IS NULL ;"` ;
if ( @ mysqlstatlist ) {
foreach my $ line ( sort @ mysqlstatlist ) {
chomp ( $ line ) ;
badprint "User '" . $ line . "' is an anonymous account.\n" ;
}
push ( @ generalrec , "Remove Anonymous User account - there is " . scalar ( @ mysqlstatlist ) . " Anonymous account." ) ;
} else {
goodprint "There is no anonymous account in all database users\n" ;
}
# Looking for Empty Password
@ mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = '' OR password IS NULL;"` ;
2009-09-17 23:13:22 +00:00
if ( @ mysqlstatlist ) {
2009-12-24 21:24:23 +00:00
foreach my $ line ( sort @ mysqlstatlist ) {
2009-09-17 23:13:22 +00:00
chomp ( $ line ) ;
badprint "User '" . $ line . "' has no password set.\n" ;
}
2015-06-12 14:09:59 +00:00
push ( @ generalrec , "Set up a Password for user with the following SQL statement ( SET PASSWORD FOR 'user'\@'SpecificDNSorIp' = PASSWORD('secure_password'); )" ) ;
2009-09-17 23:13:22 +00:00
} else {
goodprint "All database users have passwords assigned\n" ;
2015-06-12 14:09:59 +00:00
}
# Looking for User with user/ uppercase /capitalise user as password
2015-06-17 21:58:47 +00:00
@ 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)));"` ;
2015-06-12 14:09:59 +00:00
if ( @ mysqlstatlist ) {
foreach my $ line ( sort @ mysqlstatlist ) {
chomp ( $ line ) ;
badprint "User '" . $ line . "' has user name as password.\n" ;
}
2015-06-16 21:38:17 +00:00
push ( @ generalrec , "Set up a Secure Password for user\@host ( SET PASSWORD FOR 'user'\@'SpecificDNSorIp' = PASSWORD('secure_password'); )" ) ;
2015-06-12 14:09:59 +00:00
}
@ mysqlstatlist = `$mysqlcmd $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE HOST='%';"` ;
if ( @ mysqlstatlist ) {
foreach my $ line ( sort @ mysqlstatlist ) {
chomp ( $ line ) ;
badprint "User '" . $ line . "' hasn't specific host restriction.\n" ;
}
push ( @ generalrec , "Restrict Host for user\@% to user\@SpecificDNSorIp" ) ;
}
unless ( - f $ basic_password_files ) {
badprint "There is not basic password file list !" ;
return ;
}
my @ passwords = get_basic_passwords $ basic_password_files ;
infoprint "There is " . scalar ( @ passwords ) . " basic passwords in the list.\n" ;
my $ nbins = 0 ;
my $ passreq ;
if ( @ passwords ) {
foreach my $ pass ( @ passwords ) {
$ 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"` ;
2015-06-15 07:47:24 +00:00
#infoprint "There is ".scalar (@mysqlstatlist). " items.\n";
2015-06-12 14:09:59 +00:00
if ( @ mysqlstatlist ) {
foreach my $ line ( @ mysqlstatlist ) {
chomp ( $ line ) ;
badprint "User '" . $ line . "' is using weak pasword: $pass in a lower, upper or capitalize derivated version.\n" ;
$ nbins + + ;
}
}
}
}
if ( $ nbins > 0 ) {
push ( @ generalrec , $ nbins . " user(s) used basic or weaked password." ) ;
2009-09-17 23:13:22 +00:00
}
}
sub get_replication_status {
2015-04-16 14:58:48 +00:00
my $ slave_status = `$mysqlcmd $mysqllogin -Bse "show slave status\\G"` ;
2011-04-02 11:20:58 +00:00
my ( $ io_running ) = ( $ slave_status =~ /slave_io_running\S*\s+(\S+)/i ) ;
my ( $ sql_running ) = ( $ slave_status =~ /slave_sql_running\S*\s+(\S+)/i ) ;
2015-06-15 13:34:29 +00:00
my ( $ seconds_behind_master ) = ( $ slave_status =~ /seconds_behind_master\S*\s+(\S+)/i ) ;
2009-09-17 23:13:22 +00:00
if ( $ io_running eq 'Yes' && $ sql_running eq 'Yes' ) {
if ( $ myvar { 'read_only' } eq 'OFF' ) {
2011-04-01 18:17:33 +00:00
badprint "This replication slave is running with the read_only option disabled." ;
2009-09-17 23:13:22 +00:00
} else {
goodprint "This replication slave is running with the read_only option enabled." ;
}
2015-06-15 13:34:29 +00:00
if ( $ seconds_behind_master > 0 ) {
badprint "This replication slave is lagging and slave has $seconds_behind_master second(s) behind master host." ;
}
2009-09-17 23:13:22 +00:00
}
}
2008-09-08 01:16:39 +00:00
# Checks for supported or EOL'ed MySQL versions
my ( $ mysqlvermajor , $ mysqlverminor ) ;
sub validate_mysql_version {
2014-02-21 16:36:26 +00:00
( $ mysqlvermajor , $ mysqlverminor ) = $ myvar { 'version' } =~ /(\d+)\.(\d+)/ ;
2011-04-02 11:54:00 +00:00
if ( ! mysql_version_ge ( 5 ) ) {
2008-09-08 01:16:39 +00:00
badprint "Your MySQL version " . $ myvar { 'version' } . " is EOL software! Upgrade soon!\n" ;
2011-04-02 11:54:00 +00:00
} elsif ( mysql_version_ge ( 6 ) ) {
2008-09-08 01:16:39 +00:00
badprint "Currently running unsupported MySQL version " . $ myvar { 'version' } . "\n" ;
2011-04-02 11:54:00 +00:00
} else {
goodprint "Currently running supported MySQL version " . $ myvar { 'version' } . "\n" ;
2008-09-08 01:16:39 +00:00
}
}
2011-04-02 11:54:00 +00:00
# Checks if MySQL version is greater than equal to (major, minor)
sub mysql_version_ge {
my ( $ maj , $ min ) = @ _ ;
return $ mysqlvermajor > $ maj || ( $ mysqlvermajor == $ maj && $ mysqlverminor >= ( $ min || 0 ) ) ;
}
2008-09-08 01:16:39 +00:00
# Checks for 32-bit boxes with more than 2GB of RAM
my ( $ arch ) ;
sub check_architecture {
if ( $ doremote eq 1 ) { return ; }
2008-11-02 18:25:55 +00:00
if ( `uname` =~ /SunOS/ && `isainfo -b` =~ /64/ ) {
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
} elsif ( `uname` !~ /SunOS/ && `uname -m` =~ /64/ ) {
2008-09-08 01:16:39 +00:00
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
2009-09-17 23:24:33 +00:00
} elsif ( `uname` =~ /AIX/ && `bootinfo -K` =~ /64/ ) {
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
2012-12-21 12:39:36 +00:00
} elsif ( `uname` =~ /NetBSD|OpenBSD/ && `sysctl -b hw.machine` =~ /64/ ) {
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
2012-12-21 12:11:38 +00:00
} elsif ( `uname` =~ /FreeBSD/ && `sysctl -b hw.machine_arch` =~ /64/ ) {
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
2013-05-24 17:43:26 +00:00
} elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /Power Macintosh/ ) {
2013-05-24 17:24:23 +00:00
# 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
2013-05-24 17:11:08 +00:00
$ arch = 64 ;
goodprint "Operating on 64-bit architecture\n" ;
2013-05-24 17:43:26 +00:00
} elsif ( `uname` =~ /Darwin/ && `uname -m` =~ /x86_64/ ) {
2013-05-24 17:24:23 +00:00
# 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
2013-05-24 17:11:08 +00:00
$ arch = 64 ;
2013-05-24 17:43:26 +00:00
goodprint "Operating on 64-bit architecture\n" ;
2008-09-08 01:16:39 +00:00
} else {
$ arch = 32 ;
if ( $ physical_memory > 2147483648 ) {
2008-12-01 23:37:24 +00:00
badprint "Switch to 64-bit OS - MySQL cannot currently use all of your RAM\n" ;
2008-09-08 01:16:39 +00:00
} else {
goodprint "Operating on 32-bit architecture with less than 2GB RAM\n" ;
}
}
}
# Start up a ton of storage engine counts/statistics
my ( % enginestats , % enginecount , $ fragtables ) ;
sub check_storage_engines {
if ( $ opt { skipsize } eq 1 ) {
2015-06-16 21:38:17 +00:00
prettyprint "\n-------- Storage Engine Statistics -------------------------------------------\n" ;
2008-09-08 01:16:39 +00:00
infoprint "Skipped due to --skipsize option\n" ;
return ;
}
2015-06-16 21:38:17 +00:00
prettyprint "\n-------- Storage Engine Statistics -------------------------------------------\n" ;
2008-09-08 01:16:39 +00:00
my $ engines ;
2014-11-06 11:43:43 +00:00
if ( mysql_version_ge ( 5 , 1 ) ) {
2015-04-16 14:58:48 +00:00
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"` ;
2014-02-22 01:04:45 +00:00
foreach my $ line ( @ engineresults ) {
my ( $ engine , $ engineenabled ) ;
( $ engine , $ engineenabled ) = $ line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/ ;
$ 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 " ;
}
2015-06-16 21:38:17 +00:00
infoprint "Status: $engines\n" ;
2011-04-02 11:54:00 +00:00
if ( mysql_version_ge ( 5 ) ) {
2008-09-08 01:16:39 +00:00
# MySQL 5 servers can have table sizes calculated quickly from information schema
2015-06-18 19:48:03 +00:00
my @ templist = `$mysqlcmd $mysqllogin -Bse "SELECT ENGINE,SUM(DATA_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;"` ;
2008-09-08 01:16:39 +00:00
foreach my $ line ( @ templist ) {
my ( $ engine , $ size , $ count ) ;
( $ engine , $ size , $ count ) = $ line =~ /([a-zA-Z_]*)\s+(\d+)\s+(\d+)/ ;
2008-09-08 20:40:22 +00:00
if ( ! defined ( $ size ) ) { next ; }
2008-09-08 01:16:39 +00:00
$ enginestats { $ engine } = $ size ;
$ enginecount { $ engine } = $ count ;
}
2015-06-18 19:48:03 +00:00
$ 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';"` ;
2008-09-08 01:16:39 +00:00
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
2015-04-16 14:58:48 +00:00
my @ dblist = `$mysqlcmd $mysqllogin -Bse "SHOW DATABASES"` ;
2008-09-08 01:16:39 +00:00
foreach my $ db ( @ dblist ) {
chomp ( $ db ) ;
if ( $ db eq "information_schema" ) { next ; }
2015-06-18 19:48:03 +00:00
if ( $ db eq "performance_schema" ) { next ; }
if ( $ db eq "mysql" ) { next ; }
2011-04-02 11:20:58 +00:00
my @ ixs = ( 1 , 6 , 9 ) ;
2011-04-02 11:54:00 +00:00
if ( ! mysql_version_ge ( 4 , 1 ) ) {
2011-04-02 11:20:58 +00:00
# MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column
@ ixs = ( 1 , 5 , 8 ) ;
2008-09-08 01:16:39 +00:00
}
2015-04-16 14:58:48 +00:00
push ( @ tblist , map { [ ( split ) [ @ ixs ] ] } `$mysqlcmd $mysqllogin -Bse "SHOW TABLE STATUS FROM \\\`$db\\\`"` ) ;
2008-09-08 01:16:39 +00:00
}
# Parse through the table list to generate storage engine counts/statistics
$ fragtables = 0 ;
2011-04-02 11:20:58 +00:00
foreach my $ tbl ( @ tblist ) {
my ( $ engine , $ size , $ datafree ) = @$ tbl ;
2008-09-08 01:16:39 +00:00
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_rnd ( $ size ) . " (Tables: " . $ enginecount { $ engine } . ")" . "\n" ;
}
# 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\n" ;
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\n" ;
push ( @ generalrec , "Add skip-bdb to MySQL configuration to disable BDB" ) ;
}
if ( ! defined $ enginestats { 'ISAM' } && defined $ myvar { 'have_isam' } && $ myvar { 'have_isam' } eq "YES" ) {
2015-06-18 19:48:03 +00:00
badprint "MYISAM is enabled but isn't being used\n" ;
2008-11-02 18:25:55 +00:00
push ( @ generalrec , "Add skip-isam to MySQL configuration to disable ISAM (MySQL > 4.1.0)" ) ;
2008-09-08 01:16:39 +00:00
}
# Fragmented tables
if ( $ fragtables > 0 ) {
badprint "Total fragmented tables: $fragtables\n" ;
push ( @ generalrec , "Run OPTIMIZE TABLE to defragment tables for better performance" ) ;
} else {
goodprint "Total fragmented tables: $fragtables\n" ;
}
2014-12-04 07:14:02 +00:00
# Auto increments
my % tblist ;
# Find the maximum integer
2015-04-16 14:58:48 +00:00
my $ maxint = `$mysqlcmd $mysqllogin -Bse "SELECT ~0"` ;
2014-12-04 07:14:02 +00:00
# Now we build a database list, and loop through it to get storage engine stats for tables
2015-04-16 14:58:48 +00:00
my @ dblist = `$mysqlcmd $mysqllogin -Bse "SHOW DATABASES"` ;
2014-12-04 07:14:02 +00:00
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 ) ;
}
2015-04-16 14:58:48 +00:00
push ( @ { $ tblist { $ db } } , map { [ ( split ) [ @ ia ] ] } `$mysqlcmd $mysqllogin -Bse "SHOW TABLE STATUS FROM \\\`$db\\\`"` ) ;
2014-12-04 07:14:02 +00:00
}
my @ dbnames = keys % tblist ;
foreach my $ db ( @ dbnames ) {
foreach my $ tbl ( @ { $ tblist { $ db } } ) {
my ( $ name , $ autoincrement ) = @$ tbl ;
if ( $ autoincrement =~ /^\d+?$/ ) {
my $ percent = ( $ autoincrement / $ maxint ) * 100 ;
if ( $ percent >= 75 ) {
badprint "Table '$db.$name' has an autoincrement value near max capacity ($percent%)\n" ;
}
}
}
}
2008-09-08 01:16:39 +00:00
}
my % mycalc ;
sub calculations {
if ( $ mystat { 'Questions' } < 1 ) {
badprint "Your server has not answered any queries - cannot continue..." ;
exit 0 ;
}
# Per-thread memory
2011-04-02 11:54:00 +00:00
if ( mysql_version_ge ( 4 ) ) {
2008-09-08 01:16:39 +00:00
$ mycalc { 'per_thread_buffers' } = $ myvar { 'read_buffer_size' } + $ myvar { 'read_rnd_buffer_size' } + $ myvar { 'sort_buffer_size' } + $ myvar { 'thread_stack' } + $ 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 ;
# Global memory
$ mycalc { 'max_used_memory' } = $ mycalc { 'server_buffers' } + $ mycalc { "max_total_per_thread_buffers" } ;
$ mycalc { 'total_possible_used_memory' } = $ mycalc { 'server_buffers' } + $ mycalc { 'total_per_thread_buffers' } ;
$ mycalc { 'pct_physical_memory' } = int ( ( $ mycalc { 'total_possible_used_memory' } * 100 ) / $ physical_memory ) ;
# Slow queries
$ mycalc { 'pct_slow_queries' } = int ( ( $ mystat { 'Slow_queries' } / $ mystat { 'Questions' } ) * 100 ) ;
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# 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' } ;
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Key buffers
2014-02-21 17:04:38 +00:00
if ( mysql_version_ge ( 4 , 1 ) && $ myvar { 'key_buffer_size' } > 0 ) {
2008-09-08 01:16:39 +00:00
$ mycalc { 'pct_key_buffer_used' } = sprintf ( "%.1f" , ( 1 - ( ( $ mystat { 'Key_blocks_unused' } * $ myvar { 'key_cache_block_size' } ) / $ myvar { 'key_buffer_size' } ) ) * 100 ) ;
2014-02-21 17:04:38 +00:00
} else {
$ mycalc { 'pct_key_buffer_used' } = 0 ;
2008-09-08 01:16:39 +00:00
}
if ( $ mystat { 'Key_read_requests' } > 0 ) {
$ mycalc { 'pct_keys_from_mem' } = sprintf ( "%.1f" , ( 100 - ( ( $ mystat { 'Key_reads' } / $ mystat { 'Key_read_requests' } ) * 100 ) ) ) ;
2009-05-28 01:54:39 +00:00
} else {
$ mycalc { 'pct_keys_from_mem' } = 0 ;
2008-09-08 01:16:39 +00:00
}
2011-04-02 11:54:00 +00:00
if ( $ doremote eq 0 and ! mysql_version_ge ( 5 ) ) {
2011-04-02 11:20:58 +00:00
my $ size = 0 ;
$ size += ( split ) [ 0 ] for `find $myvar{'datadir'} -name "*.MYI" 2>&1 | xargs du -L $duflags 2>&1` ;
$ mycalc { 'total_myisam_indexes' } = $ size ;
2011-04-02 11:54:00 +00:00
} elsif ( mysql_version_ge ( 5 ) ) {
2015-04-16 14:58:48 +00:00
$ 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';"` ;
2008-09-08 01:16:39 +00:00
}
2011-04-02 11:20:58 +00:00
if ( defined $ mycalc { 'total_myisam_indexes' } and $ mycalc { 'total_myisam_indexes' } == 0 ) {
2011-03-08 18:38:37 +00:00
$ mycalc { 'total_myisam_indexes' } = "fail" ;
2008-09-08 01:16:39 +00:00
} elsif ( defined $ mycalc { 'total_myisam_indexes' } ) {
chomp ( $ mycalc { 'total_myisam_indexes' } ) ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Query cache
2011-04-02 11:54:00 +00:00
if ( mysql_version_ge ( 4 ) ) {
2008-09-08 01:16:39 +00:00
$ 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 ) ) ;
}
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# 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 ) ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# 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 ) ) ;
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Temporary tables
if ( $ mystat { 'Created_tmp_tables' } > 0 ) {
if ( $ mystat { 'Created_tmp_disk_tables' } > 0 ) {
2014-12-02 12:58:57 +00:00
$ mycalc { 'pct_temp_disk' } = int ( ( $ mystat { 'Created_tmp_disk_tables' } / $ mystat { 'Created_tmp_tables' } ) * 100 ) ;
2008-09-08 01:16:39 +00:00
} else {
$ mycalc { 'pct_temp_disk' } = 0 ;
}
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Table cache
if ( $ mystat { 'Opened_tables' } > 0 ) {
$ mycalc { 'table_cache_hit_rate' } = int ( $ mystat { 'Open_tables' } * 100 / $ mystat { 'Opened_tables' } ) ;
} else {
$ mycalc { 'table_cache_hit_rate' } = 100 ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Open files
if ( $ myvar { 'open_files_limit' } > 0 ) {
$ mycalc { 'pct_files_open' } = int ( $ mystat { 'Open_files' } * 100 / $ myvar { 'open_files_limit' } ) ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# 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' } ) ) ;
}
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# 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
if ( $ myvar { 'have_innodb' } eq "YES" ) {
$ mycalc { 'innodb_log_size_pct' } = ( $ myvar { 'innodb_log_file_size' } * 100 / $ myvar { 'innodb_buffer_pool_size' } ) ;
}
}
sub mysql_stats {
2015-06-16 21:38:17 +00:00
prettyprint "\n-------- Performance Metrics -------------------------------------------------\n" ;
2008-09-08 01:16:39 +00:00
# Show uptime, queries per second, connections, traffic stats
my $ qps ;
if ( $ mystat { 'Uptime' } > 0 ) { $ qps = sprintf ( "%.3f" , $ mystat { 'Questions' } / $ mystat { 'Uptime' } ) ; }
if ( $ mystat { 'Uptime' } < 86400 ) { push ( @ generalrec , "MySQL started within last 24 hours - recommendations may be inaccurate" ) ; }
infoprint "Up for: " . pretty_uptime ( $ mystat { 'Uptime' } ) . " (" . hr_num ( $ mystat { 'Questions' } ) .
" q [" . hr_num ( $ qps ) . " qps], " . hr_num ( $ mystat { 'Connections' } ) . " conn," .
" TX: " . hr_num ( $ mystat { 'Bytes_sent' } ) . ", RX: " . hr_num ( $ mystat { 'Bytes_received' } ) . ")\n" ;
infoprint "Reads / Writes: " . $ mycalc { 'pct_reads' } . "% / " . $ mycalc { 'pct_writes' } . "%\n" ;
# Memory usage
infoprint "Total buffers: " . hr_bytes ( $ mycalc { 'server_buffers' } ) . " global + " . hr_bytes ( $ mycalc { 'per_thread_buffers' } ) . " per thread ($myvar{'max_connections'} max threads)\n" ;
2014-10-23 10:37:11 +00:00
if ( $ opt { buffers } ne 0 ) {
infoprint "Global Buffers\n" ;
infoprint " +-- Key Buffer: " . hr_bytes ( $ myvar { 'key_buffer_size' } ) . "\n" ;
infoprint " +-- Max Tmp Table: " . hr_bytes ( $ mycalc { 'max_tmp_table_size' } ) . "\n" ;
if ( defined $ myvar { 'innodb_buffer_pool_size' } ) {
infoprint " +-- InnoDB Buffer Pool: " . hr_bytes ( $ myvar { 'innodb_buffer_pool_size' } ) . "\n" ;
}
if ( defined $ myvar { 'innodb_additional_mem_pool_size' } ) {
infoprint " +-- InnoDB Additional Mem Pool: " . hr_bytes ( $ myvar { 'innodb_additional_mem_pool_size' } ) . "\n" ;
}
if ( defined $ myvar { 'innodb_log_buffer_size' } ) {
infoprint " +-- InnoDB Log Buffer: " . hr_bytes ( $ myvar { 'innodb_log_buffer_size' } ) . "\n" ;
}
if ( defined $ myvar { 'query_cache_size' } ) {
infoprint " +-- Query Cache: " . hr_bytes ( $ myvar { 'query_cache_size' } ) . "\n" ;
}
2015-06-18 08:56:47 +00:00
2014-10-23 10:37:11 +00:00
infoprint "Per Thread Buffers\n" ;
infoprint " +-- Read Buffer: " . hr_bytes ( $ myvar { 'read_buffer_size' } ) . "\n" ;
infoprint " +-- Read RND Buffer: " . hr_bytes ( $ myvar { 'read_rnd_buffer_size' } ) . "\n" ;
infoprint " +-- Sort Buffer: " . hr_bytes ( $ myvar { 'sort_buffer_size' } ) . "\n" ;
infoprint " +-- Thread stack: " . hr_bytes ( $ myvar { 'thread_stack' } ) . "\n" ;
infoprint " +-- Join Buffer: " . hr_bytes ( $ myvar { 'join_buffer_size' } ) . "\n" ;
}
2014-03-21 15:32:35 +00:00
if ( $ arch && $ arch == 32 && $ mycalc { 'total_possible_used_memory' } > 2 * 1024 * 1024 * 1024 ) {
2008-09-08 01:16:39 +00:00
badprint "Allocating > 2GB RAM on 32-bit systems can cause system instability\n" ;
badprint "Maximum possible memory usage: " . hr_bytes ( $ mycalc { 'total_possible_used_memory' } ) . " ($mycalc{'pct_physical_memory'}% of installed RAM)\n" ;
} elsif ( $ mycalc { 'pct_physical_memory' } > 85 ) {
badprint "Maximum possible memory usage: " . hr_bytes ( $ mycalc { 'total_possible_used_memory' } ) . " ($mycalc{'pct_physical_memory'}% of installed RAM)\n" ;
push ( @ generalrec , "Reduce your overall MySQL memory footprint for system stability" ) ;
} else {
goodprint "Maximum possible memory usage: " . hr_bytes ( $ mycalc { 'total_possible_used_memory' } ) . " ($mycalc{'pct_physical_memory'}% of installed RAM)\n" ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Slow queries
if ( $ mycalc { 'pct_slow_queries' } > 5 ) {
badprint "Slow queries: $mycalc{'pct_slow_queries'}% (" . hr_num ( $ mystat { 'Slow_queries' } ) . "/" . hr_num ( $ mystat { 'Questions' } ) . ")\n" ;
} else {
goodprint "Slow queries: $mycalc{'pct_slow_queries'}% (" . hr_num ( $ mystat { 'Slow_queries' } ) . "/" . hr_num ( $ mystat { 'Questions' } ) . ")\n" ;
}
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" ) ; }
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Connections
if ( $ mycalc { 'pct_connections_used' } > 85 ) {
badprint "Highest connection usage: $mycalc{'pct_connections_used'}% ($mystat{'Max_used_connections'}/$myvar{'max_connections'})\n" ;
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'})\n" ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Key buffer
2009-05-28 02:16:16 +00:00
if ( ! defined ( $ mycalc { 'total_myisam_indexes' } ) and $ doremote == 1 ) {
2008-09-08 01:16:39 +00:00
push ( @ generalrec , "Unable to calculate MyISAM indexes on remote MySQL server < 5.0.0" ) ;
2011-03-08 18:38:37 +00:00
} elsif ( $ mycalc { 'total_myisam_indexes' } =~ /^fail$/ ) {
2008-09-08 01:16:39 +00:00
badprint "Cannot calculate MyISAM index size - re-run script as root user\n" ;
} elsif ( $ mycalc { 'total_myisam_indexes' } == "0" ) {
badprint "None of your MyISAM tables are indexed - add indexes immediately\n" ;
} 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' } ) . "\n" ;
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' } ) . "\n" ;
}
if ( $ mystat { 'Key_read_requests' } > 0 ) {
if ( $ mycalc { 'pct_keys_from_mem' } < 95 ) {
badprint "Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" . hr_num ( $ mystat { 'Key_read_requests' } ) . " cached / " . hr_num ( $ mystat { 'Key_reads' } ) . " reads)\n" ;
} else {
goodprint "Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% (" . hr_num ( $ mystat { 'Key_read_requests' } ) . " cached / " . hr_num ( $ mystat { 'Key_reads' } ) . " reads)\n" ;
}
} else {
# No queries have run that would use keys
}
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Query cache
2011-04-02 11:54:00 +00:00
if ( ! mysql_version_ge ( 4 ) ) {
2008-09-08 01:16:39 +00:00
# MySQL versions < 4.01 don't support query caching
push ( @ generalrec , "Upgrade MySQL to version 4+ to utilize query caching" ) ;
} elsif ( $ myvar { 'query_cache_size' } < 1 ) {
badprint "Query cache is disabled\n" ;
push ( @ adjvars , "query_cache_size (>= 8M)" ) ;
2014-03-06 20:22:02 +00:00
} elsif ( $ myvar { 'query_cache_type' } eq "OFF" ) {
2015-06-18 08:56:47 +00:00
badprint "Query cache is disabled\n" ;
push ( @ adjvars , "query_cache_type (=1)" ) ;
} elsif ( $ mystat { 'Com_select' } == 0 ) {
2008-09-08 01:16:39 +00:00
badprint "Query cache cannot be analyzed - no SELECT statements executed\n" ;
} else {
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)\n" ;
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)\n" ;
}
if ( $ mycalc { 'query_cache_prunes_per_day' } > 98 ) {
badprint "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}\n" ;
2009-05-28 01:50:29 +00:00
if ( $ myvar { 'query_cache_size' } > 128 * 1024 * 1024 ) {
2015-06-18 08:56:47 +00:00
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]" ) ;
2009-05-28 01:50:29 +00:00
} else {
2015-06-18 08:56:47 +00:00
push ( @ adjvars , "query_cache_size (> " . hr_bytes_rnd ( $ myvar { 'query_cache_size' } ) . ")" ) ;
2009-05-28 01:50:29 +00:00
}
2008-09-08 01:16:39 +00:00
} else {
goodprint "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}\n" ;
}
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Sorting
if ( $ mycalc { 'total_sorts' } == 0 ) {
# For the sake of space, we will be quiet here
# No sorts have run yet
} 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)\n" ;
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)\n" ;
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Joins
if ( $ mycalc { 'joins_without_indexes_per_day' } > 250 ) {
badprint "Joins performed without indexes: $mycalc{'joins_without_indexes'}\n" ;
push ( @ adjvars , "join_buffer_size (> " . hr_bytes ( $ myvar { 'join_buffer_size' } ) . ", or always use indexes with joins)" ) ;
push ( @ generalrec , "Adjust your join queries to always utilize indexes" ) ;
} else {
# For the sake of space, we will be quiet here
# No joins have run without indexes
}
2011-03-08 18:38:37 +00:00
2008-09-08 01:16:39 +00:00
# Temporary tables
if ( $ mystat { 'Created_tmp_tables' } > 0 ) {
if ( $ mycalc { 'pct_temp_disk' } > 25 && $ mycalc { 'max_tmp_table_size' } < 256 * 1024 * 1024 ) {
2014-12-02 12:58:57 +00:00
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)\n" ;
2008-09-08 01:16:39 +00:00
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 without LIMIT clauses" ) ;
} elsif ( $ mycalc { 'pct_temp_disk' } > 25 && $ mycalc { 'max_tmp_table_size' } >= 256 ) {
2014-12-02 12:58:57 +00:00
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)\n" ;
2008-09-08 01:16:39 +00:00
push ( @ generalrec , "Temporary table size is already large - reduce result set size" ) ;
push ( @ generalrec , "Reduce your SELECT DISTINCT queries without LIMIT clauses" ) ;
} else {
2014-12-02 12:58:57 +00:00
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)\n" ;
2008-09-08 01:16:39 +00:00
}
} else {
# For the sake of space, we will be quiet here
# No temporary tables have been created
}
# Thread cache
if ( $ myvar { 'thread_cache_size' } eq 0 ) {
badprint "Thread cache is disabled\n" ;
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)\n" ;
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)\n" ;
}
}
# Table cache
2014-06-06 01:45:16 +00:00
my $ table_cache_var = "" ;
2008-09-08 01:16:39 +00:00
if ( $ mystat { 'Open_tables' } > 0 ) {
if ( $ mycalc { 'table_cache_hit_rate' } < 20 ) {
badprint "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" . hr_num ( $ mystat { 'Open_tables' } ) . " open / " . hr_num ( $ mystat { 'Opened_tables' } ) . " opened)\n" ;
2011-04-02 11:54:00 +00:00
if ( mysql_version_ge ( 5 , 1 ) ) {
2014-06-06 01:45:16 +00:00
$ table_cache_var = "table_open_cache" ;
2008-09-08 01:16:39 +00:00
} else {
2014-06-06 01:45:16 +00:00
$ table_cache_var = "table_cache" ;
2008-09-08 01:16:39 +00:00
}
2014-11-06 11:43:43 +00:00
push ( @ adjvars , $ table_cache_var . " (> " . $ myvar { $ table_cache_var } . ")" ) ;
2014-06-05 19:57:30 +00:00
push ( @ generalrec , "Increase " . $ table_cache_var . " gradually to avoid file descriptor limits" ) ;
push ( @ generalrec , "Read this before increasing " . $ table_cache_var . " over 64: http://bit.ly/1mi7c4C" ) ;
2008-09-08 01:16:39 +00:00
} else {
goodprint "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% (" . hr_num ( $ mystat { 'Open_tables' } ) . " open / " . hr_num ( $ mystat { 'Opened_tables' } ) . " opened)\n" ;
}
}
# 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' } ) . ")\n" ;
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' } ) . ")\n" ;
}
}
# 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'}%\n" ;
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)\n" ;
}
}
# Performance options
2011-04-02 11:54:00 +00:00
if ( ! mysql_version_ge ( 4 , 1 ) ) {
2008-09-08 01:16:39 +00:00
push ( @ generalrec , "Upgrade to MySQL 4.1+ to use concurrent MyISAM inserts" ) ;
} 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" ) ;
}
if ( $ mycalc { 'pct_aborted_connections' } > 5 ) {
badprint "Connections aborted: " . $ mycalc { 'pct_aborted_connections' } . "%\n" ;
push ( @ generalrec , "Your applications are not closing MySQL connections properly" ) ;
}
2011-03-08 18:38:37 +00:00
2015-06-18 08:56:47 +00:00
}
# Recommandations for Innodb
sub mysql_innodb {
prettyprint "\n-------- InnoDB Metrics -----------------------------------------------------\n" ;
2008-09-08 01:16:39 +00:00
# InnoDB
2015-06-18 08:56:47 +00:00
unless ( defined $ myvar { 'have_innodb' } && $ myvar { 'have_innodb' } eq "YES" && defined $ enginestats { 'InnoDB' } ) {
infoprint "InnoDB is disabled." ;
if ( mysql_version_ge ( 5 , 5 ) ) {
badprint "InnoDB Storage engine is disabled. InnoDB is the default storage engine\n" ;
2008-09-08 01:16:39 +00:00
}
2015-06-18 08:56:47 +00:00
return ;
}
infoprint "InnoDB is enabled.\n" ;
infoprint "InnoDB BufferPool Size :" . hr_bytes ( $ myvar { 'innodb_buffer_pool_size' } ) . "\n" ;
infoprint "InnoDB BufferPool Inst :" . $ myvar { 'innodb_buffer_pool_instances' } . "\n" if defined ( $ myvar { 'innodb_buffer_pool_instances' } ) ;
# InnoDB Buffer Pull 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' } ) . "\n" ;
} else {
badprint "InnoDB buffer pool / data size: " . hr_bytes ( $ myvar { 'innodb_buffer_pool_size' } ) . "/" . hr_bytes ( $ enginestats { 'InnoDB' } ) . "\n" ;
push ( @ adjvars , "innodb_buffer_pool_size (>= " . hr_bytes_rnd ( $ enginestats { 'InnoDB' } ) . ")" ) ;
}
# InnoDB Buffer Pull Instances (MySQL 5.6.6+)
if ( 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' } . "\n" ;
push ( @ adjvars , "innodb_buffer_pool_instances (<= 64)" ) ;
}
# InnoDB Buffer Pull Size > 1Go
if ( $ myvar { 'innodb_buffer_pool_size' } > 1024 * 1024 * 1024
and $ myvar { 'innodb_buffer_pool_instances' } != int ( $ myvar { 'innodb_buffer_pool_size' } / ( 1024 * 1024 * 1024 ) )
) {
badprint "InnoDB buffer pool instances: " . $ myvar { 'innodb_buffer_pool_instances' } . "\n" ;
push ( @ adjvars , "innodb_buffer_pool_instances(=" . int ( $ myvar { 'innodb_buffer_pool_size' } / ( 1024 * 1024 * 1024 ) ) . ")" ) ;
} else {
if ( $ myvar { 'innodb_buffer_pool_instances' } != 1 ) {
badprint "InnoDB buffer pool <= 1Go and innodb_buffer_pool_instances(=1).\n" ;
push ( @ adjvars , "innodb_buffer_pool_instances (=1)" ) ;
2015-06-15 14:22:51 +00:00
} else {
2015-06-18 08:56:47 +00:00
goodprint "InnoDB buffer pool instances: " . $ myvar { 'innodb_buffer_pool_instances' } . "\n" ;
2015-06-15 14:22:51 +00:00
}
}
2015-06-18 08:56:47 +00:00
2014-02-21 16:44:43 +00:00
}
2008-09-08 01:16:39 +00:00
2015-06-18 08:56:47 +00:00
# InnoDB Log Waits
if ( defined $ mystat { 'Innodb_log_waits' } && $ mystat { 'Innodb_log_waits' } > 0 ) {
badprint "InnoDB log waits: " . $ mystat { 'Innodb_log_waits' } ;
push ( @ adjvars , "innodb_log_buffer_size (>= " . hr_bytes_rnd ( $ myvar { 'innodb_log_buffer_size' } ) . ")" ) ;
} else {
goodprint "InnoDB log waits: " . $ mystat { 'Innodb_log_waits' } . "\n" ;
}
}
2008-09-08 01:16:39 +00:00
# Take the two recommendation arrays and display them at the end of the output
sub make_recommendations {
2015-06-16 21:38:17 +00:00
prettyprint "\n-------- Recommendations -----------------------------------------------------\n" ;
2008-09-08 01:16:39 +00:00
if ( @ generalrec > 0 ) {
2015-06-16 21:38:17 +00:00
prettyprint "General recommendations:\n" ;
foreach ( @ generalrec ) { prettyprint " " . $ _ . "\n" ; }
2008-09-08 01:16:39 +00:00
}
if ( @ adjvars > 0 ) {
2015-06-16 21:38:17 +00:00
prettyprint "Variables to adjust:\n" ;
2008-11-02 18:25:55 +00:00
if ( $ mycalc { 'pct_physical_memory' } > 90 ) {
2015-06-16 21:38:17 +00:00
prettyprint " *** MySQL's maximum memory usage is dangerously high ***\n" .
2008-11-02 18:25:55 +00:00
" *** Add RAM before increasing MySQL buffer variables ***\n" ;
2008-09-08 01:16:39 +00:00
}
2015-06-16 21:38:17 +00:00
foreach ( @ adjvars ) { prettyprint " " . $ _ . "\n" ; }
2008-09-08 01:16:39 +00:00
}
if ( @ generalrec == 0 && @ adjvars == 0 ) {
2015-06-16 21:38:17 +00:00
prettyprint "No additional performance recommendations are available.\n"
2008-09-08 01:16:39 +00:00
}
}
2015-06-17 16:03:44 +00:00
sub close_reportfile {
close ( $ fh ) if defined ( $ fh ) ;
}
2008-09-08 01:16:39 +00:00
# ---------------------------------------------------------------------------
# BEGIN 'MAIN'
# ---------------------------------------------------------------------------
2015-06-16 21:38:17 +00:00
prettyprint " >> MySQLTuner $tunerversion - Major Hayden <major\@mhtx.net>\n" .
2008-09-08 01:16:39 +00:00
" >> Bug reports, feature requests, and downloads at http://mysqltuner.com/\n" .
" >> Run with '--help' for additional options and output filtering\n" ;
2014-08-10 17:18:16 +00:00
mysql_setup ; # Gotta login first
2015-06-15 13:34:29 +00:00
os_setup ; # Set up some OS variables
2014-08-10 17:18:16 +00:00
get_all_vars ; # Toss variables/status into hashes
2015-06-15 13:34:29 +00:00
validate_mysql_version ; # Check current MySQL version
2014-08-10 17:18:16 +00:00
check_architecture ; # Suggest 64-bit upgrade
2015-06-15 13:34:29 +00:00
check_storage_engines ; # Show enabled storage engines
security_recommendations ; # Display some security recommendations
2014-08-10 17:18:16 +00:00
calculations ; # Calculate everything we need
mysql_stats ; # Print the server stats
2015-06-18 08:56:47 +00:00
mysql_innodb ; # Print InnoDB stats
2015-06-15 13:34:29 +00:00
make_recommendations ; # Make recommendations based on stats
2015-06-17 16:03:44 +00:00
close_reportfile ; # Close reportfile if needed
2015-06-16 21:38:17 +00:00
2008-09-08 01:16:39 +00:00
# ---------------------------------------------------------------------------
# END 'MAIN'
# ---------------------------------------------------------------------------
2011-03-08 18:37:58 +00:00
# Local variables:
# indent-tabs-mode: t
# cperl-indent-level: 8
# perl-indent-level: 8
# End: