diff --git a/files/mysqltuner.pl b/files/mysqltuner.pl new file mode 100755 index 0000000000000000000000000000000000000000..f61881ce8124e14e10f2fa7bac5f25dd55e345ec --- /dev/null +++ b/files/mysqltuner.pl @@ -0,0 +1,966 @@ +#!/usr/bin/perl -w +# mysqltuner.pl - Version 1.2.0 +# High Performance MySQL Tuning Script +# Copyright (C) 2006-2011 Major Hayden - major@mhtx.net +# +# For the latest updates, please visit http://mysqltuner.com/ +# Git repository available at http://github.com/rackerhacker/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 <http://www.gnu.org/licenses/>. +# +# 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. +# +# Inspired by Matthew Montgomery's tuning-primer.sh script: +# http://forge.mysql.com/projects/view.php?id=44 +# +use strict; +use warnings; +use diagnostics; +use File::Spec; +use Getopt::Long; + +# Set up a few variables for use in the script +my $tunerversion = "1.2.0"; +my (@adjvars, @generalrec); + +# Set defaults +my %opt = ( + "nobad" => 0, + "nogood" => 0, + "noinfo" => 0, + "nocolor" => 0, + "forcemem" => 0, + "forceswap" => 0, + "host" => 0, + "socket" => 0, + "port" => 0, + "user" => 0, + "pass" => 0, + "skipsize" => 0, + "checkversion" => 0, + ); + +# 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', + 'help', + ); + +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". + "\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". + "\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". + "\n"; + exit; +} + +my $devnull = File::Spec->devnull(); + +# 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]" : "[--]" ; + +# Functions that handle the print styles +sub goodprint { print $good." ".$_[0] unless ($opt{nogood} == 1); } +sub infoprint { print $info." ".$_[0] unless ($opt{noinfo} == 1); } +sub badprint { print $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] ; } + +# 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/) { + $physical_memory = `free -b | grep Mem | awk '{print \$2}'` or memerror; + $swap_memory = `free -b | grep Swap | awk '{print \$2}'` or memerror; + } 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/) { + $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`; + $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; + } + } + chomp($physical_memory); +} + +# Checks to see if a MySQL login is possible +my ($mysqllogin,$doremote,$remotestring); +sub mysql_setup { + $doremote = 0; + $remotestring = ''; + my $command = `which mysqladmin`; + chomp($command); + if (! -e $command) { + badprint "Unable to find mysqladmin in your \$PATH. Is MySQL installed?\n"; + exit; + } + # 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; + my $loginstatus = `mysqladmin ping $mysqllogin 2>&1`; + 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; + } + } + if ( -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 = `mysqladmin ping $mysqllogin 2>&1`; + unless ($loginstatus =~ /mysqld is alive/) { + badprint "Attempted to use login credentials from Plesk, but they failed.\n"; + exit 0; + } + } elsif ( -r "/etc/mysql/debian.cnf" and $doremote == 0 ){ + # We have a debian maintenance account, use it + $mysqllogin = "--defaults-file=/etc/mysql/debian.cnf"; + my $loginstatus = `mysqladmin $mysqllogin ping 2>&1`; + if ($loginstatus =~ /mysqld is alive/) { + goodprint "Logged in using credentials from debian maintenance account.\n"; + return 1; + } else { + badprint "Attempted to use login credentials from debian maintenance account, but they failed.\n"; + exit 0; + } + } else { + # It's not Plesk or debian, we should try a login + my $loginstatus = `mysqladmin $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" ) { + 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: "; + system("stty -echo >$devnull 2>&1"); + my $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 = `mysqladmin ping $mysqllogin 2>&1`; + 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 + $dummyselect = `mysql $mysqllogin -Bse "SELECT VERSION();"`; + my @mysqlvarlist = `mysql $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ VARIABLES;"`; + foreach my $line (@mysqlvarlist) { + $line =~ /([a-zA-Z_]*)\s*(.*)/; + $myvar{$1} = $2; + } + my @mysqlstatlist = `mysql $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ STATUS;"`; + foreach my $line (@mysqlstatlist) { + $line =~ /([a-zA-Z_]*)\s*(.*)/; + $mystat{$1} = $2; + } + # Workaround for MySQL bug #59393 wrt. ignore-builtin-innodb + if (($myvar{'ignore_builtin_innodb'} || "") eq "ON") { + $myvar{'have_innodb'} = "NO"; + } + # 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 = `mysql $mysqllogin -Bse "SHOW ENGINES;" 2>$devnull`; + 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; + } + } +} + +sub security_recommendations { + print "\n-------- Security Recommendations -------------------------------------------\n"; + my @mysqlstatlist = `mysql $mysqllogin -Bse "SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE password = '' OR password IS NULL;"`; + if (@mysqlstatlist) { + foreach my $line (sort @mysqlstatlist) { + chomp($line); + badprint "User '".$line."' has no password set.\n"; + } + } else { + goodprint "All database users have passwords assigned\n"; + } +} + +sub get_replication_status { + my $slave_status = `mysql $mysqllogin -Bse "show slave status\\G"`; + my ($io_running) = ($slave_status =~ /slave_io_running\S*\s+(\S+)/i); + my ($sql_running) = ($slave_status =~ /slave_sql_running\S*\s+(\S+)/i); + if ($io_running eq 'Yes' && $sql_running eq 'Yes') { + 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."; + } + } +} + +# Checks for updates to MySQLTuner +sub validate_tuner_version { + print "\n-------- General Statistics --------------------------------------------------\n"; + if ($opt{checkversion} eq 0) { + infoprint "Skipped version check for MySQLTuner script\n"; + return; + } + my $update; + my $url = "http://mysqltuner.com/versioncheck.php?v=$tunerversion"; + if (-e "/usr/bin/curl") { + $update = `/usr/bin/curl --connect-timeout 5 '$url' 2>$devnull`; + chomp($update); + } elsif (-e "/usr/bin/wget") { + $update = `/usr/bin/wget -e timestamping=off -T 5 -O - '$url' 2>$devnull`; + chomp($update); + } + if ($update eq 1) { + badprint "There is a new version of MySQLTuner available\n"; + } elsif ($update eq 0) { + goodprint "You have the latest version of MySQLTuner\n"; + } else { + infoprint "Unable to check for the latest MySQLTuner version\n"; + } +} + +# Checks for supported or EOL'ed MySQL versions +my ($mysqlvermajor,$mysqlverminor); +sub validate_mysql_version { + ($mysqlvermajor,$mysqlverminor) = $myvar{'version'} =~ /(\d)\.(\d)/; + if (!mysql_version_ge(5)) { + badprint "Your MySQL version ".$myvar{'version'}." is EOL software! Upgrade soon!\n"; + } elsif (mysql_version_ge(6)) { + badprint "Currently running unsupported MySQL version ".$myvar{'version'}."\n"; + } else { + goodprint "Currently running supported MySQL version ".$myvar{'version'}."\n"; + } +} + +# 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)); +} + +# 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\n"; + } elsif (`uname` !~ /SunOS/ && `uname -m` =~ /64/) { + $arch = 64; + goodprint "Operating on 64-bit architecture\n"; + } elsif (`uname` =~ /AIX/ && `bootinfo -K` =~ /64/) { + $arch = 64; + goodprint "Operating on 64-bit architecture\n"; + } else { + $arch = 32; + if ($physical_memory > 2147483648) { + badprint "Switch to 64-bit OS - MySQL cannot currently use all of your RAM\n"; + } 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) { + print "\n-------- Storage Engine Statistics -------------------------------------------\n"; + infoprint "Skipped due to --skipsize option\n"; + return; + } + print "\n-------- Storage Engine Statistics -------------------------------------------\n"; + infoprint "Status: "; + my $engines; + $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 " ; + print "$engines\n"; + if (mysql_version_ge(5)) { + # MySQL 5 servers can have table sizes calculated quickly from information schema + my @templist = `mysql $mysqllogin -Bse "SELECT ENGINE,SUM(DATA_LENGTH),COUNT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema','mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;"`; + foreach my $line (@templist) { + my ($engine,$size,$count); + ($engine,$size,$count) = $line =~ /([a-zA-Z_]*)\s+(\d+)\s+(\d+)/; + if (!defined($size)) { next; } + $enginestats{$engine} = $size; + $enginecount{$engine} = $count; + } + $fragtables = `mysql $mysqllogin -Bse "SELECT COUNT(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_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 = `mysql $mysqllogin -Bse "SHOW DATABASES"`; + foreach my $db (@dblist) { + chomp($db); + if ($db eq "information_schema") { 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] ] } `mysql $mysqllogin -Bse "SHOW TABLE STATUS FROM \\\`$db\\\`"`); + } + # Parse through the table list to generate storage engine counts/statistics + $fragtables = 0; + foreach my $tbl (@tblist) { + my ($engine, $size, $datafree) = @$tbl; + 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") { + badprint "ISAM is enabled but isn't being used\n"; + 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\n"; + push(@generalrec,"Run OPTIMIZE TABLE to defragment tables for better performance"); + } else { + goodprint "Total fragmented tables: $fragtables\n"; + } +} + +my %mycalc; +sub calculations { + if ($mystat{'Questions'} < 1) { + badprint "Your server has not answered any queries - cannot continue..."; + exit 0; + } + # 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{'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); + + # 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'} ; + + # Key buffers + if (mysql_version_ge(4, 1)) { + $mycalc{'pct_key_buffer_used'} = sprintf("%.1f",(1 - (($mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'}) / $myvar{'key_buffer_size'})) * 100); + } + 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 ($doremote eq 0 and !mysql_version_ge(5)) { + 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; + } elsif (mysql_version_ge(5)) { + $mycalc{'total_myisam_indexes'} = `mysql $mysqllogin -Bse "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"; + } elsif (defined $mycalc{'total_myisam_indexes'}) { + chomp($mycalc{'total_myisam_indexes'}); + } + + # Query cache + if (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'} + $mystat{'Created_tmp_disk_tables'})) * 100); + } else { + $mycalc{'pct_temp_disk'} = 0; + } + } + + # 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; + } + + # 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 + 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 { + print "\n-------- Performance Metrics -------------------------------------------------\n"; + # 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"; + if ($mycalc{'total_possible_used_memory'} > 2*1024*1024*1024 && $arch eq 32) { + 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"; + } + + # 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"); } + } + + # 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"; + } + + # Key buffer + if (!defined($mycalc{'total_myisam_indexes'}) and $doremote == 1) { + push(@generalrec,"Unable to calculate MyISAM indexes on remote MySQL server < 5.0.0"); + } elsif ($mycalc{'total_myisam_indexes'} =~ /^fail$/) { + 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 + } + } + + # 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 ($myvar{'query_cache_size'} < 1) { + badprint "Query cache is disabled\n"; + push(@adjvars,"query_cache_size (>= 8M)"); + } elsif ($mystat{'Com_select'} == 0) { + 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"; + 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'}\n"; + } + } + + # 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"; + } + + # 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 + } + + # 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_disk_tables'} + $mystat{'Created_tmp_tables'})." total)\n"; + 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) { + badprint "Temporary tables created on disk: $mycalc{'pct_temp_disk'}% (".hr_num($mystat{'Created_tmp_disk_tables'})." on disk / ".hr_num($mystat{'Created_tmp_disk_tables'} + $mystat{'Created_tmp_tables'})." total)\n"; + 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_disk_tables'} + $mystat{'Created_tmp_tables'})." total)\n"; + } + } 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 + 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"; + if (mysql_version_ge(5, 1)) { + push(@adjvars,"table_cache (> ".$myvar{'table_open_cache'}.")"); + } else { + push(@adjvars,"table_cache (> ".$myvar{'table_cache'}.")"); + } + push(@generalrec,"Increase table_cache gradually to avoid file descriptor limits"); + } 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 + if (!mysql_version_ge(4, 1)) { + 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"); + } + + # InnoDB + if (defined $myvar{'have_innodb'} && $myvar{'have_innodb'} eq "YES" && defined $enginestats{'InnoDB'}) { + if ($myvar{'innodb_buffer_pool_size'} > $enginestats{'InnoDB'}) { + goodprint "InnoDB data size / buffer pool: ".hr_bytes($enginestats{'InnoDB'})."/".hr_bytes($myvar{'innodb_buffer_pool_size'})."\n"; + } else { + badprint "InnoDB data size / buffer pool: ".hr_bytes($enginestats{'InnoDB'})."/".hr_bytes($myvar{'innodb_buffer_pool_size'})."\n"; + push(@adjvars,"innodb_buffer_pool_size (>= ".hr_bytes_rnd($enginestats{'InnoDB'}).")"); + } + } +} + +# Take the two recommendation arrays and display them at the end of the output +sub make_recommendations { + print "\n-------- Recommendations -----------------------------------------------------\n"; + if (@generalrec > 0) { + print "General recommendations:\n"; + foreach (@generalrec) { print " ".$_."\n"; } + } + if (@adjvars > 0) { + print "Variables to adjust:\n"; + if ($mycalc{'pct_physical_memory'} > 90) { + print " *** MySQL's maximum memory usage is dangerously high ***\n". + " *** Add RAM before increasing MySQL buffer variables ***\n"; + } + foreach (@adjvars) { print " ".$_."\n"; } + } + if (@generalrec == 0 && @adjvars ==0) { + print "No additional performance recommendations are available.\n" + } + print "\n"; +} + +# --------------------------------------------------------------------------- +# BEGIN 'MAIN' +# --------------------------------------------------------------------------- +print "\n >> MySQLTuner $tunerversion - Major Hayden <major\@mhtx.net>\n". + " >> Bug reports, feature requests, and downloads at http://mysqltuner.com/\n". + " >> Run with '--help' for additional options and output filtering\n"; +mysql_setup; # Gotta login first +os_setup; # Set up some OS variables +get_all_vars; # Toss variables/status into hashes +validate_tuner_version; # Check current MySQLTuner version +validate_mysql_version; # Check current MySQL version +check_architecture; # Suggest 64-bit upgrade +check_storage_engines; # Show enabled storage engines +security_recommendations; # Display some security recommendations +calculations; # Calculate everything we need +mysql_stats; # Print the server stats +make_recommendations; # Make recommendations based on stats +# --------------------------------------------------------------------------- +# END 'MAIN' +# --------------------------------------------------------------------------- + +# Local variables: +# indent-tabs-mode: t +# cperl-indent-level: 8 +# perl-indent-level: 8 +# End: diff --git a/files/tuning-primer.sh b/files/tuning-primer.sh new file mode 100755 index 0000000000000000000000000000000000000000..1d542a6c7874d36bc3d0a13c3253680bd64b5b5c --- /dev/null +++ b/files/tuning-primer.sh @@ -0,0 +1,1634 @@ +#!/bin/sh + +# vim: ts=8 +######################################################################### +# # +# MySQL performance tuning primer script # +# Writen by: Matthew Montgomery # +# Report bugs to: https://bugs.launchpad.net/mysql-tuning-primer # +# Inspired by: MySQLARd (http://gert.sos.be/demo/mysqlar/) # +# Version: 1.6-r1 Released: 2011-08-06 # +# Licenced under GPLv2 # +# # +######################################################################### + +######################################################################### +# # +# Usage: ./tuning-primer.sh [ mode ] # +# # +# Available Modes: # +# all : perform all checks (default) # +# prompt : prompt for login credintials and socket # +# and execution mode # +# mem, memory : run checks for tunable options which # +# effect memory usage # +# disk, file : run checks for options which effect # +# i/o performance or file handle limits # +# innodb : run InnoDB checks /* to be improved */ # +# misc : run checks for that don't categorise # +# well Slow Queries, Binary logs, # +# Used Connections and Worker Threads # +######################################################################### +# # +# Set this socket variable ONLY if you have multiple instances running # +# or we are unable to find your socket, and you don't want to to be # +# prompted for input each time you run this script. # +# # +######################################################################### +socket= + +export black='\033[0m' +export boldblack='\033[1;0m' +export red='\033[31m' +export boldred='\033[1;31m' +export green='\033[32m' +export boldgreen='\033[1;32m' +export yellow='\033[33m' +export boldyellow='\033[1;33m' +export blue='\033[34m' +export boldblue='\033[1;34m' +export magenta='\033[35m' +export boldmagenta='\033[1;35m' +export cyan='\033[36m' +export boldcyan='\033[1;36m' +export white='\033[37m' +export boldwhite='\033[1;37m' + + +cecho () + +## -- Function to easliy print colored text -- ## + + # Color-echo. + # Argument $1 = message + # Argument $2 = color +{ +local default_msg="No message passed." + +message=${1:-$default_msg} # Defaults to default message. + +#change it for fun +#We use pure names +color=${2:-black} # Defaults to black, if not specified. + +case $color in + black) + printf "$black" ;; + boldblack) + printf "$boldblack" ;; + red) + printf "$red" ;; + boldred) + printf "$boldred" ;; + green) + printf "$green" ;; + boldgreen) + printf "$boldgreen" ;; + yellow) + printf "$yellow" ;; + boldyellow) + printf "$boldyellow" ;; + blue) + printf "$blue" ;; + boldblue) + printf "$boldblue" ;; + magenta) + printf "$magenta" ;; + boldmagenta) + printf "$boldmagenta" ;; + cyan) + printf "$cyan" ;; + boldcyan) + printf "$boldcyan" ;; + white) + printf "$white" ;; + boldwhite) + printf "$boldwhite" ;; +esac + printf "%s\n" "$message" + tput sgr0 # Reset to normal. + printf "$black" + +return +} + + +cechon () + +## -- Function to easliy print colored text -- ## + + # Color-echo. + # Argument $1 = message + # Argument $2 = color +{ +local default_msg="No message passed." + # Doesn't really need to be a local variable. + +message=${1:-$default_msg} # Defaults to default message. + +#change it for fun +#We use pure names +color=${2:-black} # Defaults to black, if not specified. + +case $color in + black) + printf "$black" ;; + boldblack) + printf "$boldblack" ;; + red) + printf "$red" ;; + boldred) + printf "$boldred" ;; + green) + printf "$green" ;; + boldgreen) + printf "$boldgreen" ;; + yellow) + printf "$yellow" ;; + boldyellow) + printf "$boldyellow" ;; + blue) + printf "$blue" ;; + boldblue) + printf "$boldblue" ;; + magenta) + printf "$magenta" ;; + boldmagenta) + printf "$boldmagenta" ;; + cyan) + printf "$cyan" ;; + boldcyan) + printf "$boldcyan" ;; + white) + printf "$white" ;; + boldwhite) + printf "$boldwhite" ;; +esac + printf "%s" "$message" + tput sgr0 # Reset to normal. + printf "$black" + +return +} + + +print_banner () { + +## -- Banner -- ## + +cecho " -- MYSQL PERFORMANCE TUNING PRIMER --" boldblue +cecho " - By: Matthew Montgomery -" black + +} + +## -- Find the location of the mysql.sock file -- ## + +check_for_socket () { + if [ -z "$socket" ] ; then + # Use ~/my.cnf version + if [ -f ~/.my.cnf ] ; then + cnf_socket=$(grep ^socket ~/.my.cnf | awk -F \= '{ print $2 }' | head -1) + fi + if [ -S "$cnf_socket" ] ; then + socket=$cnf_socket + elif [ -S /var/lib/mysql/mysql.sock ] ; then + socket=/var/lib/mysql/mysql.sock + elif [ -S /var/run/mysqld/mysqld.sock ] ; then + socket=/var/run/mysqld/mysqld.sock + elif [ -S /tmp/mysql.sock ] ; then + socket=/tmp/mysql.sock + else + if [ -S "$ps_socket" ] ; then + socket=$ps_socket + fi + fi + fi + if [ -S "$socket" ] ; then + echo UP > /dev/null + else + cecho "No valid socket file \"$socket\" found!" boldred + cecho "The mysqld process is not running or it is installed in a custom location." red + cecho "If you are sure mysqld is running, execute script in \"prompt\" mode or set " red + cecho "the socket= variable at the top of this script" red + exit 1 + fi +} + + +check_for_plesk_passwords () { + +## -- Check for the existance of plesk and login using it's credentials -- ## + + if [ -f /etc/psa/.psa.shadow ] ; then + mysql="mysql -S $socket -u admin -p$(cat /etc/psa/.psa.shadow)" + mysqladmin="mysqladmin -S $socket -u admin -p$(cat /etc/psa/.psa.shadow)" + else + mysql="mysql" + mysqladmin="mysqladmin" + # mysql="mysql -S $socket" + # mysqladmin="mysqladmin -S $socket" + fi +} + +check_mysql_login () { + +## -- Test for running mysql -- ## + + is_up=$($mysqladmin ping 2>&1) + if [ "$is_up" = "mysqld is alive" ] ; then + echo UP > /dev/null + # echo $is_up + elif [ "$is_up" != "mysqld is alive" ] ; then + printf "\n" + cecho "Using login values from ~/.my.cnf" + cecho "- INITIAL LOGIN ATTEMPT FAILED -" boldred + if [ -z $prompted ] ; then + find_webmin_passwords + else + return 1 + fi + + else + cecho "Unknow exit status" red + exit -1 + fi +} + +final_login_attempt () { + is_up=$($mysqladmin ping 2>&1) + if [ "$is_up" = "mysqld is alive" ] ; then + echo UP > /dev/null + elif [ "$is_up" != "mysqld is alive" ] ; then + cecho "- FINAL LOGIN ATTEMPT FAILED -" boldred + cecho "Unable to log into socket: $socket" boldred + exit 1 + fi +} + +second_login_failed () { + +## -- create a ~/.my.cnf and exit when all else fails -- ## + + cecho "Could not auto detect login info!" + cecho "Found potential sockets: $found_socks" + cecho "Using: $socket" red + read -p "Would you like to provide a different socket?: [y/N] " REPLY + case $REPLY in + yes | y | Y | YES) + read -p "Socket: " socket + ;; + esac + read -p "Do you have your login handy ? [y/N] : " REPLY + case $REPLY in + yes | y | Y | YES) + answer1='yes' + read -p "User: " user + read -rp "Password: " pass + if [ -z $pass ] ; then + export mysql="$mysql -S$socket -u$user" + export mysqladmin="$mysqladmin -S$socket -u$user" + else + export mysql="$mysql -S$socket -u$user -p$pass" + export mysqladmin="$mysqladmin -S$socket -u$user -p$pass" + fi + ;; + *) + cecho "Please create a valid login to MySQL" + cecho "Or, set correct values for 'user=' and 'password=' in ~/.my.cnf" + ;; + esac + cecho " " + read -p "Would you like me to create a ~/.my.cnf file for you? [y/N] : " REPLY + case $REPLY in + yes | y | Y | YES) + answer2='yes' + if [ ! -f ~/.my.cnf ] ; then + umask 077 + printf "[client]\nuser=$user\npassword=$pass\nsocket=$socket" > ~/.my.cnf + if [ "$answer1" != 'yes' ] ; then + exit 1 + else + final_login_attempt + return 0 + fi + else + printf "\n" + cecho "~/.my.cnf already exists!" boldred + printf "\n" + read -p "Replace ? [y/N] : " REPLY + if [ "$REPLY" = 'y' ] || [ "$REPLY" = 'Y' ] ; then + printf "[client]\nuser=$user\npassword=$pass\socket=$socket" > ~/.my.cnf + if [ "$answer1" != 'yes' ] ; then + exit 1 + else + final_login_attempt + return 0 + fi + else + cecho "Please set the 'user=' and 'password=' and 'socket=' values in ~/.my.cnf" + exit 1 + fi + fi + ;; + *) + if [ "$answer1" != 'yes' ] ; then + exit 1 + else + final_login_attempt + return 0 + fi + ;; + esac +} + +find_webmin_passwords () { + +## -- populate the .my.cnf file using values harvested from Webmin -- ## + + cecho "Testing for stored webmin passwords:" + if [ -f /etc/webmin/mysql/config ] ; then + user=$(grep ^login= /etc/webmin/mysql/config | cut -d "=" -f 2) + pass=$(grep ^pass= /etc/webmin/mysql/config | cut -d "=" -f 2) + if [ $user ] && [ $pass ] && [ ! -f ~/.my.cnf ] ; then + cecho "Setting login info as User: $user Password: $pass" + touch ~/.my.cnf + chmod 600 ~/.my.cnf + printf "[client]\nuser=$user\npassword=$pass" > ~/.my.cnf + cecho "Retrying login" + is_up=$($mysqladmin ping 2>&1) + if [ "$is_up" = "mysqld is alive" ] ; then + echo UP > /dev/null + else + second_login_failed + fi + echo + else + second_login_failed + echo + fi + else + cecho " None Found" boldred + second_login_failed + fi +} + +######################################################################### +# # +# Function to pull MySQL status variable # +# # +# Call using : # +# mysql_status \'Mysql_status_variable\' bash_dest_variable # +# # +######################################################################### + +mysql_status () { + local status=$($mysql -Bse "show /*!50000 global */ status like $1" | awk '{ print $2 }') + export "$2"=$status +} + +######################################################################### +# # +# Function to pull MySQL server runtime variable # +# # +# Call using : # +# mysql_variable \'Mysql_server_variable\' bash_dest_variable # +# - OR - # +# mysql_variableTSV \'Mysql_server_variable\' bash_dest_variable # +# # +######################################################################### + +mysql_variable () { + local variable=$($mysql -Bse "show /*!50000 global */ variables like $1" | awk '{ print $2 }') + export "$2"=$variable +} +mysql_variableTSV () { + local variable=$($mysql -Bse "show /*!50000 global */ variables like $1" | awk -F \t '{ print $2 }') + export "$2"=$variable +} + +float2int () { + local variable=$(echo "$1 / 1" | bc -l) + export "$2"=$variable +} + +divide () { + +# -- Divide two intigers -- # + + usage="$0 dividend divisor '$variable' scale" + if [ $1 -ge 1 ] ; then + dividend=$1 + else + cecho "Invalid Dividend" red + echo $usage + exit 1 + fi + if [ $2 -ge 1 ] ; then + divisor=$2 + else + cecho "Invalid Divisor" red + echo $usage + exit 1 + fi + if [ ! -n $3 ] ; then + cecho "Invalid variable name" red + echo $usage + exit 1 + fi + if [ -z $4 ] ; then + scale=2 + elif [ $4 -ge 0 ] ; then + scale=$4 + else + cecho "Invalid scale" red + echo $usage + exit 1 + fi + export $3=$(echo "scale=$scale; $dividend / $divisor" | bc -l) +} + +human_readable () { + +######################################################################### +# # +# Convert a value in to human readable size and populate a variable # +# with the result. # +# # +# Call using: # +# human_readable $value 'variable name' [ places of precision] # +# # +######################################################################### + + ## value=$1 + ## variable=$2 + scale=$3 + + if [ $1 -ge 1073741824 ] ; then + if [ -z $3 ] ; then + scale=2 + fi + divide $1 1073741824 "$2" $scale + unit="G" + elif [ $1 -ge 1048576 ] ; then + if [ -z $3 ] ; then + scale=0 + fi + divide $1 1048576 "$2" $scale + unit="M" + elif [ $1 -ge 1024 ] ; then + if [ -z $3 ] ; then + scale=0 + fi + divide $1 1024 "$2" $scale + unit="K" + else + export "$2"=$1 + unit="bytes" + fi + # let "$2"=$HR +} + +human_readable_time () { + +######################################################################## +# # +# Function to produce human readable time # +# # +######################################################################## + + usage="$0 seconds 'variable'" + if [ -z $1 ] || [ -z $2 ] ; then + cecho $usage red + exit 1 + fi + days=$(echo "scale=0 ; $1 / 86400" | bc -l) + remainder=$(echo "scale=0 ; $1 % 86400" | bc -l) + hours=$(echo "scale=0 ; $remainder / 3600" | bc -l) + remainder=$(echo "scale=0 ; $remainder % 3600" | bc -l) + minutes=$(echo "scale=0 ; $remainder / 60" | bc -l) + seconds=$(echo "scale=0 ; $remainder % 60" | bc -l) + export $2="$days days $hours hrs $minutes min $seconds sec" +} + +check_mysql_version () { + +## -- Print Version Info -- ## + + mysql_variable \'version\' mysql_version + mysql_variable \'version_compile_machine\' mysql_version_compile_machine + +if [ "$mysql_version_num" -lt 050000 ]; then + cecho "MySQL Version $mysql_version $mysql_version_compile_machine is EOL please upgrade to MySQL 4.1 or later" boldred +else + cecho "MySQL Version $mysql_version $mysql_version_compile_machine" +fi + + +} + +post_uptime_warning () { + +######################################################################### +# # +# Present a reminder that mysql must run for a couple of days to # +# build up good numbers in server status variables before these tuning # +# suggestions should be used. # +# # +######################################################################### + + mysql_status \'Uptime\' uptime + mysql_status \'Threads_connected\' threads + queries_per_sec=$(($questions/$uptime)) + human_readable_time $uptime uptimeHR + + cecho "Uptime = $uptimeHR" + cecho "Avg. qps = $queries_per_sec" + cecho "Total Questions = $questions" + cecho "Threads Connected = $threads" + echo + + if [ $uptime -gt 172800 ] ; then + cecho "Server has been running for over 48hrs." + cecho "It should be safe to follow these recommendations" + else + cechon "Warning: " boldred + cecho "Server has not been running for at least 48hrs." boldred + cecho "It may not be safe to use these recommendations" boldred + + fi + echo "" + cecho "To find out more information on how each of these" red + cecho "runtime variables effects performance visit:" red + if [ "$major_version" = '3.23' ] || [ "$major_version" = '4.0' ] || [ "$major_version" = '4.1' ] ; then + cecho "http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html" boldblue + elif [ "$major_version" = '5.0' ] || [ "$mysql_version_num" -gt '050100' ]; then + cecho "http://dev.mysql.com/doc/refman/$major_version/en/server-system-variables.html" boldblue + else + cecho "UNSUPPORTED MYSQL VERSION" boldred + exit 1 + fi + cecho "Visit http://www.mysql.com/products/enterprise/advisors.html" boldblue + cecho "for info about MySQL's Enterprise Monitoring and Advisory Service" boldblue +} + +check_slow_queries () { + +## -- Slow Queries -- ## + + cecho "SLOW QUERIES" boldblue + + mysql_status \'Slow_queries\' slow_queries + mysql_variable \'long_query_time\' long_query_time + mysql_variable \'log%queries\' log_slow_queries + + prefered_query_time=5 + if [ -e /etc/my.cnf ] ; then + if [ -z $log_slow_queries ] ; then + log_slow_queries=$(grep log-slow-queries /etc/my.cnf) + fi + fi + + if [ "$log_slow_queries" = 'ON' ] ; then + cecho "The slow query log is enabled." + elif [ "$log_slow_queries" = 'OFF' ] ; then + cechon "The slow query log is " + cechon "NOT" boldred + cecho " enabled." + elif [ -z $log_slow_queries ] ; then + cechon "The slow query log is " + cechon "NOT" boldred + cecho " enabled." + else + cecho "Error: $log_slow_queries" boldred + fi + cecho "Current long_query_time = $long_query_time sec." + cechon "You have " + cechon "$slow_queries" boldred + cechon " out of " + cechon "$questions" boldred + cecho " that take longer than $long_query_time sec. to complete" + + float2int long_query_time long_query_timeInt + + if [ $long_query_timeInt -gt $prefered_query_time ] ; then + cecho "Your long_query_time may be too high, I typically set this under $prefered_query_time sec." red + else + cecho "Your long_query_time seems to be fine" green + fi + +} + +check_binary_log () { + +## -- Binary Log -- ## + + cecho "BINARY UPDATE LOG" boldblue + + mysql_variable \'log_bin\' log_bin + mysql_variable \'max_binlog_size\' max_binlog_size + mysql_variable \'expire_logs_days\' expire_logs_days + mysql_variable \'sync_binlog\' sync_binlog + # mysql_variable \'max_binlog_cache_size\' max_binlog_cache_size + + if [ "$log_bin" = 'ON' ] ; then + cecho "The binary update log is enabled" + if [ -z "$max_binlog_size" ] ; then + cecho "The max_binlog_size is not set. The binary log will rotate when it reaches 1GB." red + fi + if [ "$expire_logs_days" -eq 0 ] ; then + cecho "The expire_logs_days is not set." boldred + cechon "The mysqld will retain the entire binary log until " red + cecho "RESET MASTER or PURGE MASTER LOGS commands are run manually" red + cecho "Setting expire_logs_days will allow you to remove old binary logs automatically" yellow + cecho "See http://dev.mysql.com/doc/refman/$major_version/en/purge-master-logs.html" yellow + fi + if [ "$sync_binlog" = 0 ] ; then + cecho "Binlog sync is not enabled, you could loose binlog records during a server crash" red + fi + else + cechon "The binary update log is " + cechon "NOT " boldred + cecho "enabled." + cecho "You will not be able to do point in time recovery" red + cecho "See http://dev.mysql.com/doc/refman/$major_version/en/point-in-time-recovery.html" yellow + fi +} + +check_used_connections () { + +## -- Used Connections -- ## + + mysql_variable \'max_connections\' max_connections + mysql_status \'Max_used_connections\' max_used_connections + mysql_status \'Threads_connected\' threads_connected + + connections_ratio=$(($max_used_connections*100/$max_connections)) + + cecho "MAX CONNECTIONS" boldblue + cecho "Current max_connections = $max_connections" + cecho "Current threads_connected = $threads_connected" + cecho "Historic max_used_connections = $max_used_connections" + cechon "The number of used connections is " + if [ $connections_ratio -ge 85 ] ; then + txt_color=red + error=1 + elif [ $connections_ratio -le 10 ] ; then + txt_color=red + error=2 + else + txt_color=green + error=0 + fi + # cechon "$max_used_connections " $txt_color + # cechon "which is " + cechon "$connections_ratio% " $txt_color + cecho "of the configured maximum." + + if [ $error -eq 1 ] ; then + cecho "You should raise max_connections" $txt_color + elif [ $error -eq 2 ] ; then + cecho "You are using less than 10% of your configured max_connections." $txt_color + cecho "Lowering max_connections could help to avoid an over-allocation of memory" $txt_color + cecho "See \"MEMORY USAGE\" section to make sure you are not over-allocating" $txt_color + else + cecho "Your max_connections variable seems to be fine." $txt_color + fi + unset txt_color +} + +check_threads() { + +## -- Worker Threads -- ## + + cecho "WORKER THREADS" boldblue + + mysql_status \'Threads_created\' threads_created1 + sleep 1 + mysql_status \'Threads_created\' threads_created2 + + mysql_status \'Threads_cached\' threads_cached + mysql_status \'Uptime\' uptime + mysql_variable \'thread_cache_size\' thread_cache_size + + historic_threads_per_sec=$(($threads_created1/$uptime)) + current_threads_per_sec=$(($threads_created2-$threads_created1)) + + cecho "Current thread_cache_size = $thread_cache_size" + cecho "Current threads_cached = $threads_cached" + cecho "Current threads_per_sec = $current_threads_per_sec" + cecho "Historic threads_per_sec = $historic_threads_per_sec" + + if [ $historic_threads_per_sec -ge 2 ] && [ $threads_cached -le 1 ] ; then + cecho "Threads created per/sec are overrunning threads cached" red + cecho "You should raise thread_cache_size" red + elif [ $current_threads_per_sec -ge 2 ] ; then + cecho "Threads created per/sec are overrunning threads cached" red + cecho "You should raise thread_cache_size" red + else + cecho "Your thread_cache_size is fine" green + fi +} + +check_key_buffer_size () { + +## -- Key buffer Size -- ## + + cecho "KEY BUFFER" boldblue + + mysql_status \'Key_read_requests\' key_read_requests + mysql_status \'Key_reads\' key_reads + mysql_status \'Key_blocks_used\' key_blocks_used + mysql_status \'Key_blocks_unused\' key_blocks_unused + mysql_variable \'key_cache_block_size\' key_cache_block_size + mysql_variable \'key_buffer_size\' key_buffer_size + mysql_variable \'datadir\' datadir + mysql_variable \'version_compile_machine\' mysql_version_compile_machine + myisam_indexes=$($mysql -Bse "/*!50000 SELECT IFNULL(SUM(INDEX_LENGTH),0) from information_schema.TABLES where ENGINE='MyISAM' */") + + if [ -z $myisam_indexes ] ; then + myisam_indexes=$(find $datadir -name '*.MYI' -exec du $duflags '{}' \; 2>&1 | awk '{ s += $1 } END { printf("%.0f\n", s )}') + fi + + if [ $key_reads -eq 0 ] ; then + cecho "No key reads?!" boldred + cecho "Seriously look into using some indexes" red + key_cache_miss_rate=0 + key_buffer_free=$(echo "$key_blocks_unused * $key_cache_block_size / $key_buffer_size * 100" | bc -l ) + key_buffer_freeRND=$(echo "scale=0; $key_buffer_free / 1" | bc -l) + else + key_cache_miss_rate=$(($key_read_requests/$key_reads)) + if [ ! -z $key_blocks_unused ] ; then + key_buffer_free=$(echo "$key_blocks_unused * $key_cache_block_size / $key_buffer_size * 100" | bc -l ) + key_buffer_freeRND=$(echo "scale=0; $key_buffer_free / 1" | bc -l) + else + key_buffer_free='Unknown' + key_buffer_freeRND=75 + fi + fi + + human_readable $myisam_indexes myisam_indexesHR + cecho "Current MyISAM index space = $myisam_indexesHR $unit" + + human_readable $key_buffer_size key_buffer_sizeHR + cecho "Current key_buffer_size = $key_buffer_sizeHR $unit" + cecho "Key cache miss rate is 1 : $key_cache_miss_rate" + cecho "Key buffer free ratio = $key_buffer_freeRND %" + + if [ "$major_version" = '5.1' ] && [ $mysql_version_num -lt 050123 ] ; then + if [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then + cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.1.23 " boldred + cecho "See Bug#5731, Bug#29419, Bug#29446" boldred + fi + fi + if [ "$major_version" = '5.0' ] && [ $mysql_version_num -lt 050052 ] ; then + if [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then + cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.0.52 " boldred + cecho "See Bug#5731, Bug#29419, Bug#29446" boldred + fi + fi + if [ "$major_version" = '4.1' -o "$major_version" = '4.0' ] && [ $key_buffer_size -ge 4294967296 ] && ( echo "x86_64 ppc64 ia64 sparc64 i686" | grep -q $mysql_version_compile_machine ) ; then + cecho "Using key_buffer_size > 4GB will cause instability in versions prior to 5.0.52 " boldred + cecho "Reduce key_buffer_size to a safe value" boldred + cecho "See Bug#5731, Bug#29419, Bug#29446" boldred + fi + + if [ $key_cache_miss_rate -le 100 ] && [ $key_cache_miss_rate -gt 0 ] && [ $key_buffer_freeRND -le 20 ]; then + cecho "You could increase key_buffer_size" boldred + cecho "It is safe to raise this up to 1/4 of total system memory;" + cecho "assuming this is a dedicated database server." + elif [ $key_buffer_freeRND -le 20 ] && [ $key_buffer_size -le $myisam_indexes ] ; then + cecho "You could increase key_buffer_size" boldred + cecho "It is safe to raise this up to 1/4 of total system memory;" + cecho "assuming this is a dedicated database server." + elif [ $key_cache_miss_rate -ge 10000 ] || [ $key_buffer_freeRND -le 50 ] ; then + cecho "Your key_buffer_size seems to be too high." red + cecho "Perhaps you can use these resources elsewhere" red + else + cecho "Your key_buffer_size seems to be fine" green + fi +} + +check_query_cache () { + +## -- Query Cache -- ## + + cecho "QUERY CACHE" boldblue + + mysql_variable \'version\' mysql_version + mysql_variable \'query_cache_size\' query_cache_size + mysql_variable \'query_cache_limit\' query_cache_limit + mysql_variable \'query_cache_min_res_unit\' query_cache_min_res_unit + mysql_status \'Qcache_free_memory\' qcache_free_memory + mysql_status \'Qcache_total_blocks\' qcache_total_blocks + mysql_status \'Qcache_free_blocks\' qcache_free_blocks + mysql_status \'Qcache_lowmem_prunes\' qcache_lowmem_prunes + + if [ -z $query_cache_size ] ; then + cecho "You are using MySQL $mysql_version, no query cache is supported." red + cecho "I recommend an upgrade to MySQL 4.1 or better" red + elif [ $query_cache_size -eq 0 ] ; then + cecho "Query cache is supported but not enabled" red + cecho "Perhaps you should set the query_cache_size" red + else + qcache_used_memory=$(($query_cache_size-$qcache_free_memory)) + qcache_mem_fill_ratio=$(echo "scale=2; $qcache_used_memory * 100 / $query_cache_size" | bc -l) + qcache_mem_fill_ratioHR=$(echo "scale=0; $qcache_mem_fill_ratio / 1" | bc -l) + + cecho "Query cache is enabled" green + human_readable $query_cache_size query_cache_sizeHR + cecho "Current query_cache_size = $query_cache_sizeHR $unit" + human_readable $qcache_used_memory qcache_used_memoryHR + cecho "Current query_cache_used = $qcache_used_memoryHR $unit" + human_readable $query_cache_limit query_cache_limitHR + cecho "Current query_cache_limit = $query_cache_limitHR $unit" + cecho "Current Query cache Memory fill ratio = $qcache_mem_fill_ratio %" + if [ -z $query_cache_min_res_unit ] ; then + cecho "No query_cache_min_res_unit is defined. Using MySQL < 4.1 cache fragmentation can be inpredictable" %yellow + else + human_readable $query_cache_min_res_unit query_cache_min_res_unitHR + cecho "Current query_cache_min_res_unit = $query_cache_min_res_unitHR $unit" + fi + if [ $qcache_free_blocks -gt 2 ] && [ $qcache_total_blocks -gt 0 ] ; then + qcache_percent_fragmented=$(echo "scale=2; $qcache_free_blocks * 100 / $qcache_total_blocks" | bc -l) + qcache_percent_fragmentedHR=$(echo "scale=0; $qcache_percent_fragmented / 1" | bc -l) + if [ $qcache_percent_fragmentedHR -gt 20 ] ; then + cecho "Query Cache is $qcache_percent_fragmentedHR % fragmented" red + cecho "Run \"FLUSH QUERY CACHE\" periodically to defragment the query cache memory" red + cecho "If you have many small queries lower 'query_cache_min_res_unit' to reduce fragmentation." red + fi + fi + + if [ $qcache_mem_fill_ratioHR -le 25 ] ; then + cecho "Your query_cache_size seems to be too high." red + cecho "Perhaps you can use these resources elsewhere" red + fi + if [ $qcache_lowmem_prunes -ge 50 ] && [ $qcache_mem_fill_ratioHR -ge 80 ]; then + cechon "However, " + cechon "$qcache_lowmem_prunes " boldred + cecho "queries have been removed from the query cache due to lack of memory" + cecho "Perhaps you should raise query_cache_size" boldred + fi + cecho "MySQL won't cache query results that are larger than query_cache_limit in size" yellow + fi + +} + +check_sort_operations () { + +## -- Sort Operations -- ## + + cecho "SORT OPERATIONS" boldblue + + mysql_status \'Sort_merge_passes\' sort_merge_passes + mysql_status \'Sort_scan\' sort_scan + mysql_status \'Sort_range\' sort_range + mysql_variable \'sort_buffer%\' sort_buffer_size + mysql_variable \'read_rnd_buffer_size\' read_rnd_buffer_size + + total_sorts=$(($sort_scan+$sort_range)) + if [ -z $read_rnd_buffer_size ] ; then + mysql_variable \'record_buffer\' read_rnd_buffer_size + fi + + ## Correct for rounding error in mysqld where 512K != 524288 ## + sort_buffer_size=$(($sort_buffer_size+8)) + read_rnd_buffer_size=$(($read_rnd_buffer_size+8)) + + human_readable $sort_buffer_size sort_buffer_sizeHR + cecho "Current sort_buffer_size = $sort_buffer_sizeHR $unit" + + human_readable $read_rnd_buffer_size read_rnd_buffer_sizeHR + cechon "Current " + if [ "$major_version" = '3.23' ] ; then + cechon "record_rnd_buffer " + else + cechon "read_rnd_buffer_size " + fi + cecho "= $read_rnd_buffer_sizeHR $unit" + + if [ $total_sorts -eq 0 ] ; then + cecho "No sort operations have been performed" + passes_per_sort=0 + fi + if [ $sort_merge_passes -ne 0 ] ; then + passes_per_sort=$(($sort_merge_passes/$total_sorts)) + else + passes_per_sort=0 + fi + + if [ $passes_per_sort -ge 2 ] ; then + cechon "On average " + cechon "$passes_per_sort " boldred + cecho "sort merge passes are made per sort operation" + cecho "You should raise your sort_buffer_size" + cechon "You should also raise your " + if [ "$major_version" = '3.23' ] ; then + cecho "record_rnd_buffer_size" + else + cecho "read_rnd_buffer_size" + fi + else + cecho "Sort buffer seems to be fine" green + fi +} + +check_join_operations () { + +## -- Joins -- ## + + cecho "JOINS" boldblue + + mysql_status \'Select_full_join\' select_full_join + mysql_status \'Select_range_check\' select_range_check + mysql_variable \'join_buffer%\' join_buffer_size + + ## Some 4K is dropped from join_buffer_size adding it back to make sane ## + ## handling of human-readable conversion ## + + join_buffer_size=$(($join_buffer_size+4096)) + + human_readable $join_buffer_size join_buffer_sizeHR 2 + + cecho "Current join_buffer_size = $join_buffer_sizeHR $unit" + cecho "You have had $select_full_join queries where a join could not use an index properly" + + if [ $select_range_check -eq 0 ] && [ $select_full_join -eq 0 ] ; then + cecho "Your joins seem to be using indexes properly" green + fi + if [ $select_full_join -gt 0 ] ; then + print_error='true' + raise_buffer='true' + fi + if [ $select_range_check -gt 0 ] ; then + cecho "You have had $select_range_check joins without keys that check for key usage after each row" red + print_error='true' + raise_buffer='true' + fi + + ## For Debuging ## + # print_error='true' + if [ $join_buffer_size -ge 4194304 ] ; then + cecho "join_buffer_size >= 4 M" boldred + cecho "This is not advised" boldred + raise_buffer= + fi + + if [ $print_error ] ; then + if [ "$major_version" = '3.23' ] || [ "$major_version" = '4.0' ] ; then + cecho "You should enable \"log-long-format\" " + elif [ "$mysql_version_num" -gt 040100 ]; then + cecho "You should enable \"log-queries-not-using-indexes\"" + fi + cecho "Then look for non indexed joins in the slow query log." + if [ $raise_buffer ] ; then + cecho "If you are unable to optimize your queries you may want to increase your" + cecho "join_buffer_size to accommodate larger joins in one pass." + printf "\n" + cecho "Note! This script will still suggest raising the join_buffer_size when" boldred + cecho "ANY joins not using indexes are found." boldred + fi + fi + + # XXX Add better tests for join_buffer_size pending mysql bug #15088 XXX # +} + +check_tmp_tables () { + +## -- Temp Tables -- ## + + cecho "TEMP TABLES" boldblue + + mysql_status \'Created_tmp_tables\' created_tmp_tables + mysql_status \'Created_tmp_disk_tables\' created_tmp_disk_tables + mysql_variable \'tmp_table_size\' tmp_table_size + mysql_variable \'max_heap_table_size\' max_heap_table_size + + + if [ $created_tmp_tables -eq 0 ] ; then + tmp_disk_tables=0 + else + tmp_disk_tables=$((created_tmp_disk_tables*100/(created_tmp_tables+created_tmp_disk_tables))) + fi + human_readable $max_heap_table_size max_heap_table_sizeHR + cecho "Current max_heap_table_size = $max_heap_table_sizeHR $unit" + + human_readable $tmp_table_size tmp_table_sizeHR + cecho "Current tmp_table_size = $tmp_table_sizeHR $unit" + + cecho "Of $created_tmp_tables temp tables, $tmp_disk_tables% were created on disk" + if [ $tmp_table_size -gt $max_heap_table_size ] ; then + cecho "Effective in-memory tmp_table_size is limited to max_heap_table_size." yellow + fi + if [ $tmp_disk_tables -ge 25 ] ; then + cecho "Perhaps you should increase your tmp_table_size and/or max_heap_table_size" boldred + cecho "to reduce the number of disk-based temporary tables" boldred + cecho "Note! BLOB and TEXT columns are not allow in memory tables." yellow + cecho "If you are using these columns raising these values might not impact your " yellow + cecho "ratio of on disk temp tables." yellow + else + cecho "Created disk tmp tables ratio seems fine" green + fi +} + +check_open_files () { + +## -- Open Files Limit -- ## + cecho "OPEN FILES LIMIT" boldblue + + mysql_variable \'open_files_limit\' open_files_limit + mysql_status \'Open_files\' open_files + + if [ -z $open_files_limit ] || [ $open_files_limit -eq 0 ] ; then + open_files_limit=$(ulimit -n) + cant_override=1 + else + cant_override=0 + fi + cecho "Current open_files_limit = $open_files_limit files" + + open_files_ratio=$(($open_files*100/$open_files_limit)) + + cecho "The open_files_limit should typically be set to at least 2x-3x" yellow + cecho "that of table_cache if you have heavy MyISAM usage." yellow + if [ $open_files_ratio -ge 75 ] ; then + cecho "You currently have open more than 75% of your open_files_limit" boldred + if [ $cant_override -eq 1 ] ; then + cecho "You should set a higer value for ulimit -u in the mysql startup script then restart mysqld" boldred + cecho "MySQL 3.23 users : This is just a guess based upon the current shell's ulimit -u value" yellow + elif [ $cant_override -eq 0 ] ; then + cecho "You should set a higher value for open_files_limit in my.cnf" boldred + else + cecho "ERROR can't determine if mysqld override of ulimit is allowed" boldred + exit 1 + fi + else + cecho "Your open_files_limit value seems to be fine" green + fi + + + +} + +check_table_cache () { + +## -- Table Cache -- ## + + cecho "TABLE CACHE" boldblue + + mysql_variable \'datadir\' datadir + mysql_variable \'table_cache\' table_cache + + ## /* MySQL +5.1 version of table_cache */ ## + mysql_variable \'table_open_cache\' table_open_cache + mysql_variable \'table_definition_cache\' table_definition_cache + + mysql_status \'Open_tables\' open_tables + mysql_status \'Opened_tables\' opened_tables + mysql_status \'Open_table_definitions\' open_table_definitions + + table_count=$($mysql -Bse "/*!50000 SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' */") + + if [ -z "$table_count" ] ; then + if [ "$UID" != "$socket_owner" ] && [ "$UID" != "0" ] ; then + cecho "You are not '$socket_owner' or 'root'" red + cecho "I am unable to determine the table_count!" red + else + table_count=$(find $datadir 2>&1 | grep -c .frm$) + fi + fi + if [ $table_open_cache ] ; then + table_cache=$table_open_cache + fi + + if [ $opened_tables -ne 0 ] && [ $table_cache -ne 0 ] ; then + table_cache_hit_rate=$(($open_tables*100/$opened_tables)) + table_cache_fill=$(($open_tables*100/$table_cache)) + elif [ $opened_tables -eq 0 ] && [ $table_cache -ne 0 ] ; then + table_cache_hit_rate=100 + table_cache_fill=$(($open_tables*100/$table_cache)) + else + cecho "ERROR no table_cache ?!" boldred + exit 1 + fi + if [ $table_cache ] && [ ! $table_open_cache ] ; then + cecho "Current table_cache value = $table_cache tables" + fi + if [ $table_open_cache ] ; then + cecho "Current table_open_cache = $table_open_cache tables" + cecho "Current table_definition_cache = $table_definition_cache tables" + fi + if [ $table_count ] ; then + cecho "You have a total of $table_count tables" + fi + + if [ $table_cache_fill -lt 95 ] ; then + cechon "You have " + cechon "$open_tables " green + cecho "open tables." + cecho "The table_cache value seems to be fine" green + elif [ $table_cache_hit_rate -le 85 -o $table_cache_fill -ge 95 ]; then + cechon "You have " + cechon "$open_tables " boldred + cecho "open tables." + cechon "Current table_cache hit rate is " + cecho "$table_cache_hit_rate%" boldred + cechon ", while " + cechon "$table_cache_fill% " boldred + cecho "of your table cache is in use" + cecho "You should probably increase your table_cache" red + else + cechon "Current table_cache hit rate is " + cechon "$table_cache_hit_rate%" green + cechon ", while " + cechon "$table_cache_fill% " green + cecho "of your table cache is in use" + cecho "The table cache value seems to be fine" green + fi + if [ $table_definition_cache ] && [ $table_definition_cache -le $table_count ] && [ $table_count -ge 100 ] ; then + cecho "You should probably increase your table_definition_cache value." red + fi +} + +check_table_locking () { + +## -- Table Locking -- ## + + cecho "TABLE LOCKING" boldblue + + mysql_status \'Table_locks_waited\' table_locks_waited + mysql_status \'Table_locks_immediate\' table_locks_immediate + mysql_variable \'concurrent_insert\' concurrent_insert + mysql_variable \'low_priority_updates\' low_priority_updates + if [ "$concurrent_insert" = 'ON' ]; then + concurrent_insert=1 + elif [ "$concurrent_insert" = 'OFF' ]; then + concurrent_insert=0 + fi + + cechon "Current Lock Wait ratio = " + if [ $table_locks_waited -gt 0 ]; then + immediate_locks_miss_rate=$(($table_locks_immediate/$table_locks_waited)) + cecho "1 : $immediate_locks_miss_rate" red + else + immediate_locks_miss_rate=99999 # perfect + cecho "0 : $questions" + fi + if [ $immediate_locks_miss_rate -lt 5000 ] ; then + cecho "You may benefit from selective use of InnoDB." + if [ "$low_priority_updates" = 'OFF' ] ; then + cecho "If you have long running SELECT's against MyISAM tables and perform" + cecho "frequent updates consider setting 'low_priority_updates=1'" + fi + if [ "$mysql_version_num" -gt 050000 ] && [ "$mysql_version_num" -lt 050500 ]; then + if [ $concurrent_insert -le 1 ] ; then + cecho "If you have a high concurrency of inserts on Dynamic row-length tables" + cecho "consider setting 'concurrent_insert=2'." + fi + elif [ "$mysql_version_num" -gt 050500 ] ; then + if [ "$concurrent_insert" = 'AUTO' ] || [ "$concurrent_insert" = 'NEVER' ] ; then + cecho "If you have a high concurrency of inserts on Dynamic row-length tables" + cecho "consider setting 'concurrent_insert=ALWAYS'." + fi + fi + else + cecho "Your table locking seems to be fine" green + fi +} + +check_table_scans () { + +## -- Table Scans -- ## + + cecho "TABLE SCANS" boldblue + + mysql_status \'Com_select\' com_select + mysql_status \'Handler_read_rnd_next\' read_rnd_next + mysql_variable \'read_buffer_size\' read_buffer_size + + if [ -z $read_buffer_size ] ; then + mysql_variable \'record_buffer\' read_buffer_size + fi + + human_readable $read_buffer_size read_buffer_sizeHR + cecho "Current read_buffer_size = $read_buffer_sizeHR $unit" + + if [ $com_select -gt 0 ] ; then + full_table_scans=$(($read_rnd_next/$com_select)) + cecho "Current table scan ratio = $full_table_scans : 1" + if [ $full_table_scans -ge 4000 ] && [ $read_buffer_size -le 2097152 ] ; then + cecho "You have a high ratio of sequential access requests to SELECTs" red + cechon "You may benefit from raising " red + if [ "$major_version" = '3.23' ] ; then + cechon "record_buffer " red + else + cechon "read_buffer_size " red + fi + cecho "and/or improving your use of indexes." red + elif [ $read_buffer_size -gt 8388608 ] ; then + cechon "read_buffer_size is over 8 MB " red + cecho "there is probably no need for such a large read_buffer" red + + else + cecho "read_buffer_size seems to be fine" green + fi + else + cecho "read_buffer_size seems to be fine" green + fi +} + + +check_innodb_status () { + +## -- InnoDB -- ## + + ## See http://bugs.mysql.com/59393 + + if [ "$mysql_version_num" -lt 050603 ] ; then + mysql_variable \'have_innodb\' have_innodb + fi + if [ "$mysql_version_num" -lt 050500 ] && [ "$have_innodb" = "YES" ] ; then + innodb_enabled=1 + fi + if [ "$mysql_version_num" -ge 050500 ] && [ "$mysql_version_num" -lt 050512 ] ; then + mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb + if [ "$ignore_builtin_innodb" = "ON" ] || [ $have_innodb = "NO" ] ; then + innodb_enabled=0 + else + innodb_enabled=1 + fi + elif [ "$major_version" = '5.5' ] && [ "$mysql_version_num" -ge 050512 ] ; then + mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb + if [ "$ignore_builtin_innodb" = "ON" ] ; then + innodb_enabled=0 + else + innodb_enabled=1 + fi + elif [ "$mysql_version_num" -ge 050600 ] && [ "$mysql_version_num" -lt 050603 ] ; then + mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb + if [ "$ignore_builtin_innodb" = "ON" ] || [ $have_innodb = "NO" ] ; then + innodb_enabled=0 + else + innodb_enabled=1 + fi + elif [ "$major_version" = '5.6' ] && [ "$mysql_version_num" -ge 050603 ] ; then + mysql_variable \'ignore_builtin_innodb\' ignore_builtin_innodb + if [ "$ignore_builtin_innodb" = "ON" ] ; then + innodb_enabled=0 + else + innodb_enabled=1 + fi + fi + if [ "$innodb_enabled" = 1 ] ; then + mysql_variable \'innodb_buffer_pool_size\' innodb_buffer_pool_size + mysql_variable \'innodb_additional_mem_pool_size\' innodb_additional_mem_pool_size + mysql_variable \'innodb_fast_shutdown\' innodb_fast_shutdown + mysql_variable \'innodb_flush_log_at_trx_commit\' innodb_flush_log_at_trx_commit + mysql_variable \'innodb_locks_unsafe_for_binlog\' innodb_locks_unsafe_for_binlog + mysql_variable \'innodb_log_buffer_size\' innodb_log_buffer_size + mysql_variable \'innodb_log_file_size\' innodb_log_file_size + mysql_variable \'innodb_log_files_in_group\' innodb_log_files_in_group + mysql_variable \'innodb_safe_binlog\' innodb_safe_binlog + mysql_variable \'innodb_thread_concurrency\' innodb_thread_concurrency + + cecho "INNODB STATUS" boldblue + innodb_indexes=$($mysql -Bse "/*!50000 SELECT IFNULL(SUM(INDEX_LENGTH),0) from information_schema.TABLES where ENGINE='InnoDB' */") + innodb_data=$($mysql -Bse "/*!50000 SELECT IFNULL(SUM(DATA_LENGTH),0) from information_schema.TABLES where ENGINE='InnoDB' */") + + if [ ! -z "$innodb_indexes" ] ; then + + mysql_status \'Innodb_buffer_pool_pages_data\' innodb_buffer_pool_pages_data + mysql_status \'Innodb_buffer_pool_pages_misc\' innodb_buffer_pool_pages_misc + mysql_status \'Innodb_buffer_pool_pages_free\' innodb_buffer_pool_pages_free + mysql_status \'Innodb_buffer_pool_pages_total\' innodb_buffer_pool_pages_total + + mysql_status \'Innodb_buffer_pool_read_ahead_seq\' innodb_buffer_pool_read_ahead_seq + mysql_status \'Innodb_buffer_pool_read_requests\' innodb_buffer_pool_read_requests + + mysql_status \'Innodb_os_log_pending_fsyncs\' innodb_os_log_pending_fsyncs + mysql_status \'Innodb_os_log_pending_writes\' innodb_os_log_pending_writes + mysql_status \'Innodb_log_waits\' innodb_log_waits + + mysql_status \'Innodb_row_lock_time\' innodb_row_lock_time + mysql_status \'Innodb_row_lock_waits\' innodb_row_lock_waits + + human_readable $innodb_indexes innodb_indexesHR + cecho "Current InnoDB index space = $innodb_indexesHR $unit" + human_readable $innodb_data innodb_dataHR + cecho "Current InnoDB data space = $innodb_dataHR $unit" + percent_innodb_buffer_pool_free=$(($innodb_buffer_pool_pages_free*100/$innodb_buffer_pool_pages_total)) + cecho "Current InnoDB buffer pool free = "$percent_innodb_buffer_pool_free" %" + + else + cecho "Cannot parse InnoDB stats prior to 5.0.x" red + $mysql -s -e "SHOW /*!50000 ENGINE */ INNODB STATUS\G" + fi + + human_readable $innodb_buffer_pool_size innodb_buffer_pool_sizeHR + cecho "Current innodb_buffer_pool_size = $innodb_buffer_pool_sizeHR $unit" + cecho "Depending on how much space your innodb indexes take up it may be safe" + cecho "to increase this value to up to 2 / 3 of total system memory" + else + cecho "No InnoDB Support Enabled!" boldred + fi +} + +total_memory_used () { + +## -- Total Memory Usage -- ## + cecho "MEMORY USAGE" boldblue + + mysql_variable \'read_buffer_size\' read_buffer_size + mysql_variable \'read_rnd_buffer_size\' read_rnd_buffer_size + mysql_variable \'sort_buffer_size\' sort_buffer_size + mysql_variable \'thread_stack\' thread_stack + mysql_variable \'max_connections\' max_connections + mysql_variable \'join_buffer_size\' join_buffer_size + mysql_variable \'tmp_table_size\' tmp_table_size + mysql_variable \'max_heap_table_size\' max_heap_table_size + mysql_variable \'log_bin\' log_bin + mysql_status \'Max_used_connections\' max_used_connections + + if [ "$major_version" = "3.23" ] ; then + mysql_variable \'record_buffer\' read_buffer_size + mysql_variable \'record_rnd_buffer\' read_rnd_buffer_size + mysql_variable \'sort_buffer\' sort_buffer_size + fi + + if [ "$log_bin" = "ON" ] ; then + mysql_variable \'binlog_cache_size\' binlog_cache_size + else + binlog_cache_size=0 + fi + + if [ $max_heap_table_size -le $tmp_table_size ] ; then + effective_tmp_table_size=$max_heap_table_size + else + effective_tmp_table_size=$tmp_table_size + fi + + + per_thread_buffers=$(echo "($read_buffer_size+$read_rnd_buffer_size+$sort_buffer_size+$thread_stack+$join_buffer_size+$binlog_cache_size)*$max_connections" | bc -l) + per_thread_max_buffers=$(echo "($read_buffer_size+$read_rnd_buffer_size+$sort_buffer_size+$thread_stack+$join_buffer_size+$binlog_cache_size)*$max_used_connections" | bc -l) + + mysql_variable \'innodb_buffer_pool_size\' innodb_buffer_pool_size + if [ -z $innodb_buffer_pool_size ] ; then + innodb_buffer_pool_size=0 + fi + + mysql_variable \'innodb_additional_mem_pool_size\' innodb_additional_mem_pool_size + if [ -z $innodb_additional_mem_pool_size ] ; then + innodb_additional_mem_pool_size=0 + fi + + mysql_variable \'innodb_log_buffer_size\' innodb_log_buffer_size + if [ -z $innodb_log_buffer_size ] ; then + innodb_log_buffer_size=0 + fi + + mysql_variable \'key_buffer_size\' key_buffer_size + + mysql_variable \'query_cache_size\' query_cache_size + if [ -z $query_cache_size ] ; then + query_cache_size=0 + fi + + global_buffers=$(echo "$innodb_buffer_pool_size+$innodb_additional_mem_pool_size+$innodb_log_buffer_size+$key_buffer_size+$query_cache_size" | bc -l) + + + max_memory=$(echo "$global_buffers+$per_thread_max_buffers" | bc -l) + total_memory=$(echo "$global_buffers+$per_thread_buffers" | bc -l) + + pct_of_sys_mem=$(echo "scale=0; $total_memory*100/$physical_memory" | bc -l) + + if [ $pct_of_sys_mem -gt 90 ] ; then + txt_color=boldred + error=1 + else + txt_color= + error=0 + fi + + human_readable $max_memory max_memoryHR + cecho "Max Memory Ever Allocated : $max_memoryHR $unit" $txt_color + human_readable $per_thread_buffers per_thread_buffersHR + cecho "Configured Max Per-thread Buffers : $per_thread_buffersHR $unit" $txt_color + human_readable $global_buffers global_buffersHR + cecho "Configured Max Global Buffers : $global_buffersHR $unit" $txt_color + human_readable $total_memory total_memoryHR + cecho "Configured Max Memory Limit : $total_memoryHR $unit" $txt_color +# human_readable $effective_tmp_table_size effective_tmp_table_sizeHR +# cecho "Plus $effective_tmp_table_sizeHR $unit per temporary table created" + human_readable $physical_memory physical_memoryHR + cecho "Physical Memory : $physical_memoryHR $unit" $txt_color + if [ $error -eq 1 ] ; then + printf "\n" + cecho "Max memory limit exceeds 90% of physical memory" $txt_color + else + cecho "Max memory limit seem to be within acceptable norms" green + fi + unset txt_color +} + +## Required Functions ## + +login_validation () { + check_for_socket # determine the socket location -- 1st login + check_for_plesk_passwords # determine the login method -- 2nd login + check_mysql_login # determine if mysql is accepting login -- 3rd login + export major_version=$($mysql -Bse "SELECT SUBSTRING_INDEX(VERSION(), '.', +2)") +# export mysql_version_num=$($mysql -Bse "SELECT LEFT(REPLACE(SUBSTRING_INDEX(VERSION(), '-', +1), '.', ''),4)" ) + export mysql_version_num=$($mysql -Bse "SELECT VERSION()" | + awk -F \. '{ printf "%02d", $1; printf "%02d", $2; printf "%02d", $3 }') + +} + +shared_info () { + export major_version=$($mysql -Bse "SELECT SUBSTRING_INDEX(VERSION(), '.', +2)") + # export mysql_version_num=$($mysql -Bse "SELECT LEFT(REPLACE(SUBSTRING_INDEX(VERSION(), '-', +1), '.', ''),4)" ) + export mysql_version_num=$($mysql -Bse "SELECT VERSION()" | + awk -F \. '{ printf "%02d", $1; printf "%02d", $2; printf "%02d", $3 }') + mysql_status \'Questions\' questions +# socket_owner=$(find -L $socket -printf '%u\n') + socket_owner=$(ls -nH $socket | awk '{ print $3 }') +} + + +get_system_info () { + + export OS=$(uname) + + # Get information for various UNIXes + if [ "$OS" = 'Darwin' ]; then + ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) + found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') + export physical_memory=$(sysctl -n hw.memsize) + export duflags='' + elif [ "$OS" = 'FreeBSD' ] || [ "$OS" = 'OpenBSD' ]; then + ## On FreeBSD must be root to locate sockets. + ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) + found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') + export physical_memory=$(sysctl -n hw.realmem) + export duflags='' + elif [ "$OS" = 'Linux' ] ; then + ## Includes SWAP + ## export physical_memory=$(free -b | grep -v buffers | awk '{ s += $2 } END { printf("%.0f\n", s ) }') + ps_socket=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }' | head -1) + found_socks=$(netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }') + export physical_memory=$(awk '/^MemTotal/ { printf("%.0f", $2*1024 ) }' < /proc/meminfo) + export duflags='-b' + elif [ "$OS" = 'SunOS' ] ; then + ps_socket=$(netstat -an | awk '/mysql(.*)?.sock/ { print $5 }' | head -1) + found_socks=$(netstat -an | awk '/mysql(.*)?.sock/ { print $5 }') + export physical_memory=$(prtconf | awk '/^Memory\ size:/ { print $3*1048576 }') + fi + if [ -z $(which bc) ] ; then + echo "Error: Command line calculator 'bc' not found!" + exit + fi +} + + +## Optional Components Groups ## + +banner_info () { + shared_info + print_banner ; echo + check_mysql_version ; echo + post_uptime_warning ; echo +} + +misc () { + shared_info + check_slow_queries ; echo + check_binary_log ; echo + check_threads ; echo + check_used_connections ; echo + check_innodb_status ; echo +} + +memory () { + shared_info + total_memory_used ; echo + check_key_buffer_size ; echo + check_query_cache ; echo + check_sort_operations ; echo + check_join_operations ; echo +} + +file () { + shared_info + check_open_files ; echo + check_table_cache ; echo + check_tmp_tables ; echo + check_table_scans ; echo + check_table_locking ; echo +} + +all () { + banner_info + misc + memory + file +} + +prompt () { + prompted='true' + read -p "Username [anonymous] : " user + read -rp "Password [<none>] : " pass + cecho " " + read -p "Socket [ /var/lib/mysql/mysql.sock ] : " socket + if [ -z $socket ] ; then + export socket='/var/lib/mysql/mysql.sock' + fi + + if [ -z $pass ] ; then + export mysql="mysql -S $socket -u$user" + export mysqladmin="mysqladmin -S $socket -u$user" + else + export mysql="mysql -S $socket -u$user -p$pass" + export mysqladmin="mysqladmin -S $socket -u$user -p$pass" + fi + + check_for_socket + check_mysql_login + + if [ $? = 1 ] ; then + exit 1 + fi + read -p "Mode to test - banner, file, misc, mem, innodb, [all] : " REPLY + if [ -z $REPLY ] ; then + REPLY='all' + fi + case $REPLY in + banner | BANNER | header | HEADER | head | HEAD) + banner_info + ;; + misc | MISC | miscelaneous ) + misc + ;; + mem | memory | MEM | MEMORY ) + memory + ;; + file | FILE | disk | DISK ) + file + ;; + innodb | INNODB ) + innodb + ;; + all | ALL ) + cecho " " + all + ;; + * ) + cecho "Invalid Mode! Valid options are 'banner', 'misc', 'memory', 'file', 'innodb' or 'all'" boldred + exit 1 + ;; + esac +} + +## Address environmental differences ## +get_system_info +# echo $ps_socket + +if [ -z "$1" ] ; then + login_validation + mode='ALL' +elif [ "$1" = "prompt" ] || [ "$1" = "PROMPT" ] ; then + mode=$1 +elif [ "$1" != "prompt" ] || [ "$1" != "PROMPT" ] ; then + login_validation + mode=$1 +fi + +case $mode in + all | ALL ) + cecho " " + all + ;; + mem | memory | MEM | MEMORY ) + cecho " " + memory + ;; + file | FILE | disk | DISK ) + cecho " " + file + ;; + banner | BANNER | header | HEADER | head | HEAD ) + banner_info + ;; + misc | MISC | miscelaneous ) + cecho " " + misc + ;; + innodb | INNODB ) + banner_info + check_innodb_status ; echo + ;; + prompt | PROMPT ) + prompt + ;; + *) + cecho "usage: $0 [ all | banner | file | innodb | memory | misc | prompt ]" boldred + exit 1 + ;; +esac diff --git a/tasks/main.yml b/tasks/main.yml index fc03a3c9249e7a5366daac9d2a2fe1f9697b6167..b0afdbb39c8b917471e7dd0bfb7335812789ea92 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -77,4 +77,15 @@ - include: backup.yml tags: 'cron' + - name: "MySQL | Copy Tuning Scripts" + copy: + src='{{ item }}' + dest='/usr/local/bin/{{ item }}' + owner=root + group=root + mode='755' + with_items: + - 'mysqltuner.pl' + - 'tuning-primer.sh' + when: '"mysql" not in excluded_roles'