Assembla home | Assembla project page
 

Changeset 13

Show
Ignore:
Timestamp:
08/12/07 18:51:10 (1 year ago)
Author:
major
Message:

Tremendous overhaul of massive proportions.
Thanks to suggestions from Mike Jackson, BK Box, Bryan Tholen, and Shawn Ashlee.
MySQLTuner's new home is now at: http://rackerhacker.com/mysqltuner/
The big feature changes will be documented there.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • mysqltuner.pl

    r12 r13  
    44use diagnostics; 
    55use Getopt::Long; 
    6 use Text::Wrap; 
    7 $Text::Wrap::columns = 72; 
    86 
    97# Set defaults 
     
    1210        "nogood" => 0, 
    1311        "noinfo" => 0, 
    14         "notitle" => 0, 
    15         "withexplain" => 0, 
    16         "explainonly" => 0, 
    1712        "nocolor" => 0, 
    1813    ); 
     
    2318        'nogood', 
    2419        'noinfo', 
    25         'notitle', 
    26         'withexplain', 
    27         'explainonly', 
    2820        'nocolor', 
    2921        'help', 
     
    4537        "       --nobad         Remove negative/suggestion responses\n". 
    4638        "       --noinfo        Remove informational responses\n". 
    47         "       --notitle       Remove section title headers\n". 
    48         "       --withexplain   Add verbose explanations\n". 
    49         "       --explainonly   Provide only long text explanations, no bullets/titles\n". 
    5039        "       --nocolor       Don't print output in color\n". 
    5140        "\n"; 
     
    6554} 
    6655 
    67 if ($opt{explainonly} == 1) { 
    68     $opt{nogood} = 1; 
    69     $opt{noinfo} = 1; 
    70     $opt{nobad} = 1; 
    71     $opt{notitle} = 1; 
    72     $opt{withexplain} = 1; 
    73 } 
    74  
    7556sub goodprint { 
    7657    if ($opt{nogood} == 1) { return 0; } 
     
    8970    my $text = shift; 
    9071    print $bad." ".$text; 
    91 } 
    92  
    93 sub titleprint { 
    94     if ($opt{notitle} == 1) { return 0; } 
    95     my $text = shift; 
    96     print $text; 
    97 } 
    98  
    99 my $exptext; 
    100 sub explainprint { 
    101     if ($opt{withexplain} == 0) { return 0; } 
    102     my $text = shift; 
    103     if ($opt{explainonly} == 0) { 
    104         print "\n".wrap("","",$text)."\n\n"; 
    105     } else { 
    106         print "\n".wrap("","",$text)."\n"; 
    107     } 
    10872} 
    10973 
     
    12690} 
    12791 
    128 sub mysql_install_ok { 
     92my $mysqllogin; 
     93sub mysql_setup { 
    12994    my $command = `which mysqladmin`; 
    13095    chomp($command); 
     
    13398        exit; 
    13499    } 
    135 } 
    136  
    137 my $mysqllogin; 
    138 sub setup_mysql_login { 
    139100    if ( -r "/etc/psa/.psa.shadow" ) { 
    140101        # It's a Plesk box, use the available credentials 
     
    187148} 
    188149 
    189 my %mystat; 
    190 my %myvar; 
     150my (%mystat,%myvar); 
    191151sub get_all_vars { 
    192152    my @mysqlvarlist = `mysql $mysqllogin -Bse "SHOW /*!50000 GLOBAL */ VARIABLES;"`; 
     
    205165sub validate_mysql_version { 
    206166    ($mysqlvermajor,$mysqlverminor) = $myvar{'version'} =~ /(\d)\.(\d)/; 
    207     if ($mysqlvermajor < 4 || ($mysqlvermajor == 4 && $mysqlverminor < 1)) { 
    208         badprint "This script will not work with MySQL < 4.1\n"; 
    209         exit 0; 
    210     } elsif ($1 == 4 && $2 == 0) { 
     167    if ($mysqlvermajor < 5) { 
    211168        badprint "Your MySQL version ".$myvar{'version'}." is EOL software!  Upgrade soon!\n"; 
    212     } elsif ($1 == 5 && $2 == 1) { 
     169    } elsif ($mysqlvermajor == 5 && $mysqlverminor == 1) { 
    213170        badprint "Currently running supported MySQL version ".$myvar{'version'}." (BETA - USE CAUTION)\n"; 
    214171    } else { 
     
    275232} 
    276233 
    277 sub mysql_initial_stats { 
     234my %mycalc; 
     235sub calculations { 
     236    if ($mystat{'Questions'} < 1) { 
     237        badprint "Your server has not answered any queries - cannot continue..."; 
     238        exit 0; 
     239    } 
     240    # Per-thread memory 
     241    if ($mysqlvermajor > 3) { 
     242        $mycalc{'per_thread_buffers'} = $myvar{'read_buffer_size'} + $myvar{'read_rnd_buffer_size'} + $myvar{'sort_buffer_size'} + $myvar{'thread_stack'} + $myvar{'join_buffer_size'}; 
     243    } else { 
     244        $mycalc{'per_thread_buffers'} = $myvar{'record_buffer'} + $myvar{'record_rnd_buffer'} + $myvar{'sort_buffer'} + $myvar{'thread_stack'} + $myvar{'join_buffer_size'}; 
     245    } 
     246    $mycalc{'total_per_thread_buffers'} = $mycalc{'per_thread_buffers'} * $myvar{'max_connections'}; 
     247    $mycalc{'max_total_per_thread_buffers'} = $mycalc{'per_thread_buffers'} * $mystat{'Max_used_connections'}; 
     248 
     249    # Server-wide memory 
     250    $mycalc{'max_tmp_table_size'} = ($myvar{'tmp_table_size'} > $myvar{'max_heap_table_size'}) ? $myvar{'max_heap_table_size'} : $myvar{'tmp_table_size'} ; 
     251    $mycalc{'server_buffers'} = $myvar{'key_buffer_size'} + $mycalc{'max_tmp_table_size'}; 
     252    $mycalc{'server_buffers'} += (defined $myvar{'innodb_buffer_pool_size'}) ? $myvar{'innodb_buffer_pool_size'} : 0 ; 
     253    $mycalc{'server_buffers'} += (defined $myvar{'innodb_additional_mem_pool_size'}) ? $myvar{'innodb_additional_mem_pool_size'} : 0 ; 
     254    $mycalc{'server_buffers'} += (defined $myvar{'innodb_log_buffer_size'}) ? $myvar{'innodb_log_buffer_size'} : 0 ; 
     255    $mycalc{'server_buffers'} += (defined $myvar{'query_cache_size'}) ? $myvar{'query_cache_size'} : 0 ; 
     256 
     257    # Global memory 
     258    $mycalc{'max_used_memory'} = $mycalc{'server_buffers'} + $mycalc{"max_total_per_thread_buffers"}; 
     259    $mycalc{'total_possible_used_memory'} = $mycalc{'server_buffers'} + $mycalc{'total_per_thread_buffers'}; 
     260    $mycalc{'pct_physical_memory'} = int(($mycalc{'total_possible_used_memory'} * 100) / $physical_memory); 
     261 
     262    # Slow queries 
     263    $mycalc{'pct_slow_queries'} = int(($mystat{'Slow_queries'}/$mystat{'Questions'}) * 100); 
     264     
     265    # Connections 
     266    $mycalc{'pct_connections_used'} = int(($mystat{'Max_used_connections'}/$myvar{'max_connections'}) * 100); 
     267    $mycalc{'pct_connections_used'} = ($mycalc{'pct_connections_used'} > 100) ? 100 : $mycalc{'pct_connections_used'} ; 
     268     
     269    # Key buffers 
     270    if ($mysqlvermajor > 3) { 
     271        $mycalc{'pct_key_buffer_used'} = sprintf("%.1f",(1 - (($mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'}) / $myvar{'key_buffer_size'})) * 100); 
     272    } 
     273    if ($mystat{'Key_read_requests'} > 0) { 
     274        $mycalc{'pct_keys_from_mem'} = sprintf("%.1f",(100 - (($mystat{'Key_reads'} / $mystat{'Key_read_requests'}) * 100))); 
     275    } 
     276    if ($mysqlvermajor < 5) { 
     277        $mycalc{'total_myisam_indexes'} = `find $myvar{'datadir'} -name '*.MYI' 2>&1 | xargs du $duflags '{}' 2>&1 | awk '{ s += \$1 } END { print s }'`; 
     278        if ($mycalc{'total_myisam_indexes'} =~ /^0\n$/) { $mycalc{'total_myisam_indexes'} = "fail"; } 
     279    } else { 
     280        $mycalc{'total_myisam_indexes'} = `mysql $mysqllogin -Bse "/*!50000 SELECT SUM(INDEX_LENGTH) from information_schema.TABLES where ENGINE='MyISAM' */"`; 
     281    } 
     282    chomp($mycalc{'total_myisam_indexes'}); 
     283     
     284    # Query cache 
     285    if ($mysqlvermajor > 3) { 
     286        $mycalc{'query_cache_efficiency'} = sprintf("%.1f",($mystat{'Qcache_hits'} / ($mystat{'Com_select'} + $mystat{'Qcache_hits'})) * 100); 
     287        $mycalc{'pct_query_cache_used'} = sprintf("%.1f",100 - ($mystat{'Qcache_free_memory'} / $myvar{'query_cache_size'}) * 100); 
     288        if ($mystat{'Qcache_lowmem_prunes'} == 0) { 
     289            $mycalc{'query_cache_prunes_per_day'} = 0; 
     290        } else { 
     291            $mycalc{'query_cache_prunes_per_day'} = int($mystat{'Qcache_lowmem_prunes'} / ($mystat{'Uptime'}/86400)); 
     292        } 
     293    } 
     294     
     295    # Sorting 
     296    $mycalc{'total_sorts'} = $mystat{'Sort_scan'} + $mystat{'Sort_range'}; 
     297    if ($mycalc{'total_sorts'} > 0) { 
     298        $mycalc{'pct_temp_sort_table'} = int(($mystat{'Sort_merge_passes'} / $mycalc{'total_sorts'}) * 100); 
     299    } 
     300     
     301    # Joins 
     302    $mycalc{'joins_without_indexes'} = $mystat{'Select_range_check'} + $mystat{'Select_full_join'}; 
     303     
     304    # Temporary tables 
     305    if ($mystat{'Created_tmp_tables'} > 0) { 
     306        if ($mystat{'Created_tmp_disk_tables'} > 0) { 
     307            $mycalc{'pct_temp_disk'} = int(($mystat{'Created_tmp_disk_tables'} / $mystat{'Created_tmp_tables'}) * 100); 
     308        } else { 
     309            $mycalc{'pct_temp_disk'} = 0; 
     310        } 
     311    } 
     312     
     313    # Table cache 
     314    if ($mystat{'Opened_tables'} > 0) { 
     315        $mycalc{'table_cache_hit_rate'} = int($mystat{'Open_tables'}*100/$mystat{'Opened_tables'}); 
     316    } else { 
     317        $mycalc{'table_cache_hit_rate'} = 100; 
     318    } 
     319     
     320    # Open files 
     321    if ($mystat{'Open_files'} > 0 && $myvar{'open_files_limit'} > 0) { 
     322        $mycalc{'pct_files_open'} = int($mystat{'Open_files'}*100/$myvar{'open_files_limit'}); 
     323    } 
     324     
     325    # Table locks 
     326    if ($mystat{'Table_locks_immediate'} > 0) { 
     327        if ($mystat{'Table_locks_waited'} == 0) { 
     328            $mycalc{'pct_table_locks_immediate'} = 100; 
     329        } else { 
     330            $mycalc{'pct_table_locks_immediate'} = int($mystat{'Table_locks_immediate'}*100/($mystat{'Table_locks_waited'} + $mystat{'Table_locks_immediate'})); 
     331        } 
     332    } 
     333     
     334    # Thread cache 
     335    $mycalc{'thread_cache_hit_rate'} = int(100 - (($mystat{'Threads_created'} / $mystat{'Connections'}) * 100)); 
     336     
     337    #foreach my $key (sort keys %mycalc) { print "$key\t\t-> \t".$mycalc{$key}."\n"; } 
     338
     339 
     340my (@decvars, @incvars, @generalrec); 
     341sub mysql_stats { 
     342    print "-------- General Statistics --------------------------------------------------\n"; 
     343 
    278344    # Show uptime, queries per second, connections, traffic stats 
    279     my $qps = sprintf("%.3f",$mystat{'Questions'}/$mystat{'Uptime'}); 
     345    my $qps; 
     346    if ($mystat{'Uptime'} > 0) { $qps = sprintf("%.3f",$mystat{'Questions'}/$mystat{'Uptime'}); } 
     347    if ($mystat{'Uptime'} < 86400) { push(@generalrec,"MySQL started within last 24 hours - recommendations may be inaccurate"); } 
    280348    infoprint "Up for: ".pretty_uptime($mystat{'Uptime'})." (".hr_num($mystat{'Questions'}). 
    281349        " q [".hr_num($qps)." qps], ".hr_num($mystat{'Connections'})." conn,". 
    282350        " TX: ".hr_num($mystat{'Bytes_sent'}).", RX: ".hr_num($mystat{'Bytes_received'}).")\n"; 
    283     if ($mystat{'Uptime'} < 86400) { 
    284         badprint "MySQL has been recently restarted - results cannot be trusted\n"; 
    285     } 
    286 
    287  
    288 sub check_memory { 
    289     titleprint "------ MEMORY USAGE ------\n"; 
    290     # The purpose of this section is to make sure you're not going to end up in swap or crashing the box 
    291     # by having buffers that are set too large 
    292     # 
    293     # PER-THREAD BUFFERS: 
    294     #   join_buffer_size - helps joins that don't use indexes (default 128M) 
    295     my $join_buffer_size = $myvar{'join_buffer_size'}; 
    296     #   read_buffer_size - for sequential scans (default 128K) 
    297     my $read_buffer_size = $myvar{'read_buffer_size'}; 
    298     #   read_rnd_buffer_size - helps ORDER BY (default 256K) 
    299     my $read_rnd_buffer_size = $myvar{'read_rnd_buffer_size'}; 
    300     #   sort_buffer_size - helps SORT/ORDER BY (default 2M) 
    301     my $sort_buffer_size = $myvar{'sort_buffer_size'}; 
    302     #   thread_stack - stack size per thread (default 192K) [MySQL says not to adjust this] 
    303     my $thread_stack = $myvar{'thread_stack'}; 
    304     # 
    305     # PER-THREAD BUFFER CALCULATIONS: 
    306     my $thread_buffers = $read_buffer_size + $read_rnd_buffer_size + $sort_buffer_size + $thread_stack + $join_buffer_size; 
    307     my $total_thread_buffers = $thread_buffers * $myvar{'max_connections'}; 
    308     my $max_thread_buffers = $thread_buffers * $mystat{'Max_used_connections'}; 
    309     # 
    310     # GLOBAL BUFFERS: 
    311     #   innodb_buffer_pool_size - general buffer for InnoDB (default 8M) 
    312     my $innodb_buffer_pool_size = (defined $myvar{'innodb_buffer_pool_size'}) ? $myvar{'innodb_buffer_pool_size'} : 0 ; 
    313     #   innodb_additional_mem_pool_size - stores internal InnoDB table data (default 1M) 
    314     my $innodb_additional_mem_pool_size = (defined $myvar{'innodb_additional_mem_pool_size'}) ? $myvar{'innodb_additional_mem_pool_size'} : 0 ; 
    315     #   innodb_log_buffer_size - holds InnoDB log before writing to disk (default 1M) 
    316     my $innodb_log_buffer_size = (defined $myvar{'innodb_log_buffer_size'}) ? $myvar{'innodb_log_buffer_size'} : 0 ; 
    317     #   key_buffer_size - holds all index data from MyISAM tables (default 8M) 
    318     my $key_buffer_size = $myvar{'key_buffer_size'}; 
    319     #   query_cache_size - holds query results (default 0) [still allocated when query_cache_type = 0] 
    320     my $query_cache_size = (defined $myvar{'query_cache_size'}) ? $myvar{'query_cache_size'} : 0 ; 
    321     #   max_heap_table_size / tmp_table_size - hold temporary table data from queries 
    322     #   ** The effective temporary table size is the smaller number between these two 
    323     my $eff_tmp_table_size; 
    324     if ($myvar{'tmp_table_size'} > $myvar{'max_heap_table_size'}) { 
    325         $eff_tmp_table_size = $myvar{'max_heap_table_size'}; 
    326     } else { 
    327         $eff_tmp_table_size = $myvar{'tmp_table_size'}; 
    328     } 
    329     # 
    330     # GLOBAL BUFFER CALCULATIONS: 
    331     my $global_buffers = $innodb_buffer_pool_size + $innodb_additional_mem_pool_size + $innodb_log_buffer_size + $key_buffer_size + $query_cache_size + $eff_tmp_table_size; 
    332     # 
    333     # FINAL BUFFER/MEMORY CALCULATIONS: 
    334     my $max_memory = $global_buffers + $max_thread_buffers; 
    335     my $total_memory = $global_buffers + $total_thread_buffers; 
    336     my $pct_physical_memory = int(($total_memory * 100) / $physical_memory); 
    337      
    338     infoprint "Per-thread buffers are ".hr_bytes_rnd($thread_buffers).", total ".hr_bytes_rnd($total_thread_buffers). 
    339         " ($myvar{'max_connections'} connections)\n"; 
    340     infoprint "Max ever allocated is ".hr_bytes_rnd($max_memory)." (".hr_bytes_rnd($thread_buffers). 
    341         " per-thread * $mystat{'Max_used_connections'} connections + ".hr_bytes_rnd($global_buffers)." global)\n"; 
    342     if ($pct_physical_memory > 85) { 
    343         badprint "DANGER - MySQL is configured to use $pct_physical_memory% (".hr_bytes($total_memory). 
    344             ") of physical memory (".hr_bytes($physical_memory).")\n"; 
    345         $exptext = "Your current memory settings are dangerous as they exceed the maximum amount of memory on your ". 
    346             "server if it was running at full capacity.  By reducing your current memory usage, you will decrease the ". 
    347             "chances of out of memory errors and general instability."; 
    348     } else { 
    349         goodprint "MySQL is configured to use $pct_physical_memory% (".hr_bytes($total_memory). 
    350             ") of physical memory (".hr_bytes($physical_memory).")\n"; 
    351         $exptext = "Your current memory settings are reasonable as they do not exceed the maximum amount of memory on ". 
    352             "your server.  If you find that this script suggests increasing buffers in later tests, you may not have additional memory ". 
    353             "available for the expansion of those buffers."; 
    354     } 
    355     explainprint "This script's calculations show MySQL's memory usage at full capacity.  ". 
    356         "This means that all connections are at their maximum with all buffers being fully used.  ".$exptext; 
    357      
    358 
    359  
    360 sub check_slow_queries { 
    361     titleprint "------ SLOW QUERIES ------\n"; 
    362     # If the server hasn't received any queries, then we can't calculate a slow query percentage 
    363     if ($mystat{'Questions'} > 0) { 
    364         my $slowquerypct = int(($mystat{'Slow_queries'}/$mystat{'Questions'}) * 100); 
    365         if ($slowquerypct > 5) { 
    366             badprint "$slowquerypct% of all queries take more than ".$myvar{'long_query_time'}." sec - optimization is recommended\n"; 
    367         } elsif ($slowquerypct <= 5 && $slowquerypct >= 1) { 
    368             goodprint "$slowquerypct% of all queries take more than ".$myvar{'long_query_time'}." sec\n"; 
    369         } else { 
    370             goodprint "Less than 1% of all queries take more than ".$myvar{'long_query_time'}." sec\n"; 
    371         } 
    372     } 
    373     # Best case scenario would be slow query log enabled with a long_query_time of 10 or less 
    374     if ($myvar{'log_slow_queries'} =~ /ON/) { 
    375         if ($myvar{'long_query_time'} <= 10) { 
    376             goodprint "Slow query log is enabled, and long_query_time is reasonable ($myvar{'long_query_time'} sec)\n"; 
    377             $exptext = "Your slow query settings are already at an optimal level."; 
    378         } else { 
    379             print $bad. " Slow query log is enabled, but long_query_time is too long ($myvar{'long_query_time'} sec)\n"; 
    380             $exptext = "Although your slow query log is enabled, a query must exceed $myvar{'long_query_time'} seconds ". 
    381                 "to appear in the log which is much too long.  Reduce the long_query_time to 10 or less to make your ". 
    382                 "slow query log more effective."; 
    383         } 
    384     } else { 
    385         if ($myvar{'long_query_time'} <= 10) { 
    386             badprint "Slow query log is disabled, but long_query_time is reasonable ($myvar{'long_query_time'} sec)\n"; 
    387             $exptext = "While your long_query_time is set to a value less than 10, your slow query log is currently ". 
    388                 "disabled. This will prevent you from auditing your slow queries."; 
    389         } else { 
    390             badprint "Slow query log is disabled, and long_query_time is too long ($myvar{'long_query_time'} sec)\n"; 
    391             $exptext = "To audit your server's slow queries, you should enable the slow query log and reduce your ". 
    392                 "long_query_time to 10 seconds or less."; 
    393         } 
    394     } 
    395     explainprint "The slow query log will allow you to see which queries are taking too long to execute.  ".$exptext; 
    396 
    397  
    398 sub check_connections { 
    399     titleprint "------ CONNECTION LIMITS ------\n"; 
    400     # We're looking at two things here: 
    401     #   How many connections have been used so far and how close is the connection limit? 
    402     #   How many connections can overflow into the back_log? 
    403     # 
    404     # If the maximum connections used is over 85% of the limit, that's a little close. 
    405     # However, if the maximum connections used is less than 10%, that's just wasted memory. 
    406     my $connpct = int(($mystat{'Max_used_connections'}/$myvar{'max_connections'}) * 100); 
    407     if ($connpct > 85) { 
    408         badprint "$connpct% of connections have been used ". 
    409             "(".$mystat{'Max_used_connections'}."/".$myvar{'max_connections'}.")". 
    410             " - Increase the max_connections variable\n"; 
    411             $exptext = "Your historical connection usage is too high relative to your set maximum.  The max_connections variable should be increased."; 
    412     } elsif ($connpct < 10) { 
    413         badprint "$connpct% of connections have been used ". 
    414             "(".$mystat{'Max_used_connections'}."/".$myvar{'max_connections'}.")". 
    415             " - Reduce max_connections\n"; 
    416             $exptext = "Your historical connection usage is voo low relative to your set maximum.  MySQL is wasting resources by allowing". 
    417                 " so many concurrent connections."; 
    418     } else { 
    419         goodprint "$connpct% of connections have been used ". 
    420             "(".$mystat{'Max_used_connections'}."/".$myvar{'max_connections'}.")\n"; 
    421             $exptext = "These settings are appropriate for your historical usage."; 
    422     } 
    423     # If the back_log is less than 50, there's a chance that connections will be forcefully rejected 
    424     if ($myvar{'back_log'} < 50) { 
    425         badprint "Your listen queue back_log is too low - you should raise this to 50-256\n"; 
    426     } else { 
    427         goodprint "Your listen queue back_log is set to a reasonable level (".$myvar{'back_log'}.")\n"; 
    428     } 
    429     explainprint "You can currently handle ".($myvar{'max_connections'}+$myvar{'back_log'})." total connections, which includes ". 
    430         $myvar{'max_connections'}." active connections with ".$myvar{'back_log'}." more in queue. ".$exptext; 
    431 
    432  
    433 sub check_key_buffer { 
    434     titleprint "------ KEY BUFFER ------\n"; 
    435     my $myisamindexes; 
    436     if ($mysqlvermajor < 5) { 
    437         $myisamindexes = `find $myvar{'datadir'} -name '*.MYI' 2>&1 | xargs du $duflags '{}' 2>&1 | awk '{ s += \$1 } END { print s }'`; 
    438         if ($myisamindexes =~ /^0\n$/) { 
    439             badprint "Unable to complete calculations - run this script with root privileges\n"; 
    440             return 0; 
    441         } 
    442     } else { 
    443         $myisamindexes = `mysql $mysqllogin -Bse "/*!50000 SELECT SUM(INDEX_LENGTH) from information_schema.TABLES where ENGINE='MyISAM' */"`; 
    444     } 
    445     chomp($myisamindexes); 
    446     # There's several variables that come into play with regards to key buffers 
    447     #   Key_blocks_unused - number of unused key blocks in the key cache (will decrease over time) 
    448     #   Key_blocks_used - number of used blocks in the key cache (will increase over time) 
    449     #   Key_read_requests - number of requests to read a key block from the cache (this should be high) 
    450     #   Key_reads - number of requests for a key that had to be read from a disk (this should be low) 
    451     if ($mystat{'Key_reads'} == 0) { 
    452         badprint "Your queries are not using any indexes - no recommendations can be made\n"; 
    453         return 0; 
    454     } 
    455     # BUFFER CALCULATIONS: 
    456     #   key_buffer_use_pct - how much of the key buffer is being used 
    457     #       if this gets too high, and key_buffer_size is less than the total 
    458     #       size of the MyISAM indexes, then it should be raised 
    459     #   key_from_mem_pct - the percent of key requests that come from the cache (rather than the disk) 
    460     #       if this starts to increase, the key_buffer_size is too small and  
    461     #       keys are being thrown out and replaced by other keys 
    462     my $key_buffer_use_pct = sprintf("%.1f",(1 - (($mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'}) / $myvar{'key_buffer_size'})) * 100); 
    463     my $key_from_mem_pct = sprintf("%.1f",(100 - (($mystat{'Key_reads'} / $mystat{'Key_read_requests'}) * 100))); 
    464     my $raise_key_buffer = 0; 
    465     if ($key_buffer_use_pct >= 85) { 
    466         $raise_key_buffer = 1       # Key buffer is almost full - raise it 
    467     } elsif ($key_buffer_use_pct < 85 && $key_buffer_use_pct >= 25) { 
    468         $raise_key_buffer = 0;      # Key buffer is reasonable 
    469     } else { 
    470         $raise_key_buffer = -1;     # Key buffer is too big - lower it 
    471     } 
    472     if ($key_from_mem_pct >= 95) { 
    473         $raise_key_buffer = 0;      # Key buffer is being utilized well, no need to change a thing 
    474     } elsif ($key_from_mem_pct < 95 && $key_from_mem_pct >= 80) { 
    475         $raise_key_buffer += 1;     # Key buffer is probably set to the default, should be raised 
    476     } elsif ($key_from_mem_pct < 80) { 
    477         $raise_key_buffer += 2;     # This is really, really bad - raise the buffer! 
    478     } 
    479     infoprint "The key buffer is $key_buffer_use_pct% used, and $key_from_mem_pct% of key requests come from memory\n"; 
    480     if ($myisamindexes < $myvar{'key_buffer_size'} && $raise_key_buffer >= 1) { 
    481         badprint "Your key_buffer_size (".hr_bytes_rnd($myvar{'key_buffer_size'}).") is higher than your total MyISAM indexes (".hr_bytes_rnd($myisamindexes).")\n"; 
    482     } else { 
    483         infoprint "Total MyISAM index size is ".hr_bytes_rnd($myisamindexes). 
    484             " (current key_buffer_size ".hr_bytes_rnd($myvar{'key_buffer_size'}).")\n"; 
    485     } 
    486     if ($raise_key_buffer >= 1) { 
    487         badprint "Raise the key_buffer_size for better indexing performance in MyISAM tables\n"; 
    488     } elsif ($raise_key_buffer == 0) { 
    489         goodprint "Your key_buffer_size is set to a reasonable level\n"; 
    490     } else { 
    491         badprint "Lower the key_buffer_size to use the resources elsewhere\n"; 
    492     } 
    493     if ($myvar{'max_seeks_for_key'} > 100) { 
    494         badprint "Reduce max_seeks_for_key to force MySQL to prefer indexes over table scans\n"; 
    495     } 
    496 
    497  
    498 sub check_query_cache { 
    499     titleprint "------ QUERY CACHE ------\n"; 
    500     # If query cache support isn't compiled into MySQL, we fail here 
    501     if (!defined $myvar{'have_query_cache'}) { 
    502         badprint "Your MySQL server does not support query caching - upgrade MySQL\n"; 
    503         return 0; 
    504     } 
    505     # If the query cache is disabled, we fail here 
    506     if ($myvar{'query_cache_size'} == 0) { 
    507         badprint "The query cache is disabled - set the query_cache_size > 0 to enable it\n"; 
    508         return 0; 
    509     } 
    510     # If there haven't been any selects, then we can't do much 
    511     if ($mystat{'Com_select'} == 0) { 
    512         infoprint "No SELECT statements have been run - no recommendations can be made\n"; 
    513         return 0; 
    514     } 
    515     # QUERY CACHE VARIABLES/STATUS: 
    516     #   Com_select - number of SELECTS executed that didn't use the query cache 
    517     #   Qcache_free_memory - memory available for use in the query cache 
    518     #   Qcache_hits - number of queries that pulled a result from the query cache 
    519     #   Qcache_lowmem_prunes - amount of times MySQL has had to clear a full query cache 
    520     #   query_cache_limit - the biggest result allowed per query 
    521     #   query_cache_size - the size of the query cache (if it's 0, query cache is disabled) 
    522     #    
    523     # QUERY CACHE CALCULATIONS: 
    524     #   query_cache_efficiency - % of SELECTS that pull from query cache (should be > 20%) 
    525     my $query_cache_efficiency = sprintf("%.1f",($mystat{'Qcache_hits'} / ($mystat{'Com_select'} + $mystat{'Qcache_hits'})) * 100); 
    526     #   qcache_pct_free - % of query cache free 
    527     my $qcache_pct_free = sprintf("%.1f",($mystat{'Qcache_free_memory'} / $myvar{'query_cache_size'}) * 100); 
    528     my $problem = 0; 
    529     infoprint "$query_cache_efficiency% of SELECTS used query cache (Stats: $qcache_pct_free% free, " 
    530         .hr_bytes_rnd($myvar{'query_cache_size'})." total, $mystat{'Qcache_lowmem_prunes'} prunes)\n"; 
    531     # When the prunes get to be pretty high, either too many queries are making it into the query cache 
    532     # that don't belong there, or the queries are too big and the query_cache_limit needs to be reduced. 
    533     if ($mystat{'Qcache_lowmem_prunes'} > 50) { 
    534         badprint "Cache cleared $mystat{'Qcache_lowmem_prunes'} times due to low memory - increase query_cache_size\n"; 
    535         $problem = 1; 
    536     } 
    537     # If the query_cache_efficiency is less than 20%, then too many selects are not being pulled from 
    538     # the query_cache.  This could mean that the results exceed query_cache_limit, or the queries are 
    539     # using the SQL_NO_CACHE modifier (which throw these recommendations way off). 
    540     if ($query_cache_efficiency <= 20) { 
    541         badprint "Very few queries used query cache - increase query_cache_limit or adjust queries\n"; 
    542         $problem = 1; 
    543     } 
    544     # If the query_cache efficiency is over 20% and the prunes are under 50, then everything should be good. 
    545     if ($problem == 0) { 
    546         goodprint "Your query cache is configured properly\n"; 
    547     } 
    548 
    549  
    550 sub check_sort { 
    551     titleprint "------ SORTING ------\n"; 
    552     # SORTING VARIABLES/STATUS: 
    553     #   read_rnd_buffer_size - sorts are read from this buffer after the sort is complete, helps ORDER BY 
    554     #   sort_buffer_size - per-thread buffer used for sorting, helps ORDER/GROUP BY 
    555     #   Sort_merge_passes - number of merge passes that the sort algorithm has had to do (bad if high, raise sort_buffer_size) 
    556     #   Sort_range - sorts done using ranges 
    557     #   Sort_scan - sorts done by scanning table 
    558     # LOGIC: 
    559     #   If a SELECT does a sequential table scan, then Sort_scan gets incremented 
    560     #   If a SELECT retrieves a range of rows, then Sort_range gets incremented 
    561     #   If a sort is too big for the sort_buffer_size, temporary tables are made, sorted, and then Sort_merge_passes gets incremented 
    562     my $total_sorts = $mystat{'Sort_scan'} + $mystat{'Sort_range'}; 
    563     if ($total_sorts == 0) { 
    564         infoprint "No sorts have been performed - no recommendations can be made\n"; 
    565         return 0; 
    566     } 
    567     my $temp_sort_table_pct = int(($mystat{'Sort_merge_passes'} / $total_sorts) * 100); 
    568     infoprint "$total_sorts sorts have occurred with $temp_sort_table_pct% requiring temporary tables\n"; 
    569     if ($temp_sort_table_pct > 10) { 
    570         badprint "Increase sorting performance by raising sort_buffer_size and read_rnd_buffer_size\n"; 
    571     } else { 
    572         goodprint "Your sorting buffers are reasonable\n"; 
    573     } 
    574 
    575  
    576 sub check_join { 
    577     titleprint "------ JOINS ------\n"; 
    578     # JOIN VARIABLES/STATUS: 
    579     #   join_buffer_size - buffer used to join tables when indexes can't be utilized 
    580     #   Select_full_join - number of joins not using indexes 
    581     #   Select_range_check - number of joins where MySQL was uncertain on whether it could use indexes to find a range 
    582     # LOGIC: 
    583     #   If Select_full_join or Select_range_check are greater than 0, then the join queries need to be fixed 
    584     #   Technically, the performance hit of Select_range_check is as bad as Select_full_join, so they should be summed 
    585     my $joins_without_indexes = $mystat{'Select_range_check'} + $mystat{'Select_range_check'}; 
    586     if ($joins_without_indexes > 0) { 
    587         infoprint "$joins_without_indexes joins did not use indexes, join buffer is ".hr_bytes_rnd($myvar{'join_buffer_size'})."\n"; 
    588         badprint "Adjust your joins to utilize indexes (if impossible, increase join_buffer_size)\n"; 
    589         badprint "NOTE: This script will always suggest raising the join_buffer_size\n"; 
    590     } else { 
    591         goodprint "Your joins are using indexes appropriately, join buffer is not necessary\n"; 
    592     } 
    593 
    594  
    595 sub check_temporary_tables { 
    596     titleprint "------ TEMPORARY TABLES ------\n"; 
    597     # TEMPORARY TABLE VARIABLES/STATUS: 
    598     #   Created_tmp_disk_tables - number of temporary tables created on disk 
    599     #   Created_tmp_tables - number of temporary tables created in memory 
    600     #   max_heap_table_size - maximum size for MEMORY tables (also limits tmp_table_size) 
    601     #   tmp_table_size - max size for all temporary tables in memory (limited by max_heap_table_size) 
    602     # LOGIC: 
    603     #   If the ratio of Created_tmp_disk_tables to Created_tmp_tables increases, this is bad 
    604     #   To reduce temporary tables on disk, the tmp_table_size needs to be increased (and possibly max_heap_table_size) 
    605     if ($mystat{'Created_tmp_tables'} == 0) { 
    606         infoprint "No temporary tables have been created since the server started\n"; 
    607         return 0; 
    608     } 
    609     my $tmp_disk_pct = int(($mystat{'Created_tmp_disk_tables'} / $mystat{'Created_tmp_tables'}) * 100); 
    610     if ($tmp_disk_pct > 25) { 
    611         badprint "$tmp_disk_pct% of temp tables were created on disk\n"; 
    612         badprint "Increase tmp_table_size (which is limited by max_heap_table_size)\n"; 
    613     } else { 
    614         goodprint "$tmp_disk_pct% of temp tables were created on disk - this is reasonable\n"; 
    615     } 
    616 
    617  
    618 sub check_other_buffers { 
    619     titleprint "------ OTHER BUFFERS ------\n"; 
    620     #  bulk_insert_buffer_size = buffer for large inserts (default: 8M) 
    621     infoprint "Your bulk_insert_buffer_size is ".hr_bytes_rnd($myvar{'bulk_insert_buffer_size'}). 
    622         " - increase this for large bulk inserts\n"; 
    623 
    624  
    625 sub performance_options { 
    626     titleprint "------ PERFORMANCE OPTIONS ------\n"; 
    627     # concurrent_insert = enables simultaneous insert/select on MyISAM tables w/o holes 
    628     # It's ON/OFF in MySQL 4.1 and 0/1 in MySQL 5.x 
    629     if ($myvar{'concurrent_insert'} eq "ON" || $myvar{'concurrent_insert'} > 0) { 
    630         goodprint "Concurrent inserts/selects in MyISAM tables are enabled\n"; 
    631     } else { 
    632         badprint "Concurrent inserts/selects in MyISAM tables are disabled - enable concurrent_insert\n"; 
     351     
     352    # Memory usage 
     353    if ($mycalc{'pct_physical_memory'} > 85) { 
     354        badprint "Maximum possible memory usage: ".hr_bytes($mycalc{'total_possible_used_memory'})." ($mycalc{'pct_physical_memory'}% of installed RAM)\n"; 
     355        push(@generalrec,"Reduce your overall MySQL memory footprint for system stability"); 
     356    } else { 
     357        goodprint "Maximum possible memory usage: ".hr_bytes($mycalc{'total_possible_used_memory'})." ($mycalc{'pct_physical_memory'}% of installed RAM)\n"; 
     358    } 
     359     
     360    # Slow queries 
     361    if ($mycalc{'pct_slow_queries'} > 5) { 
     362        badprint "Slow queries: $mycalc{'pct_slow_queries'}%\n"; 
     363    } else { 
     364        goodprint "Slow queries: $mycalc{'pct_slow_queries'}%\n"; 
     365    } 
     366    if ($myvar{'long_query_time'} > 5) { push(@decvars,"long_query_time (<= 5)"); } 
     367     
     368    # Connections 
     369    if ($mycalc{'pct_connections_used'} > 85) { 
     370        badprint "Highest connection usage: $mycalc{'pct_connections_used'}%\n"; 
     371        push(@incvars,"max_connections (> ".$myvar{'max_connections'}.")"); 
     372        push(@decvars,"wait_timeout (< ".$myvar{'wait_timeout'}.")","interactive_timeout (< ".$myvar{'interactive_timeout'}.")"); 
     373        push(@generalrec,"Reduce or eliminate persistent connections to reduce connection usage") 
     374    } else { 
     375        goodprint "Highest usage of available connections: $mycalc{'pct_connections_used'}%\n"; 
     376    } 
     377     
     378    # Key buffer 
     379    if ($mycalc{'total_myisam_indexes'} =~ /^fail$/) {  
     380        badprint "Cannot calculate MyISAM index size - re-run script as root user\n"; 
     381    } elsif ($mycalc{'total_myisam_indexes'} == "0") { 
     382        badprint "None of your MyISAM tables are indexed - add indexes immediately\n"; 
     383    } else { 
     384        if ($myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'}) { 
     385            badprint "Key buffer size / total MyISAM indexes: ".hr_bytes($myvar{'key_buffer_size'})."/".hr_bytes($mycalc{'total_myisam_indexes'})."\n"; 
     386            push(@incvars,"key_buffer_size (> ".hr_bytes($mycalc{'total_myisam_indexes'}).")"); 
     387        } else { 
     388            goodprint "Key buffer size / total MyISAM indexes: ".hr_bytes($myvar{'key_buffer_size'})."/".hr_bytes($mycalc{'total_myisam_indexes'})."\n"; 
     389        } 
     390        if ($mystat{'Key_read_requests'} > 0) { 
     391            if ($mycalc{'pct_keys_from_mem'} < 95) { 
     392                badprint "Key buffer hit rate: $mycalc{'pct_keys_from_mem'}%\n"; 
     393            } else { 
     394                goodprint "Key buffer hit rate: $mycalc{'pct_keys_from_mem'}%\n"; 
     395            } 
     396        } else { 
     397            # For the sake of space, we will be quiet here 
     398            # No queries have run that would use keys 
     399        } 
     400    } 
     401    if ($mysqlvermajor > 3 && $myvar{'max_seeks_for_key'} > 100) { push(@decvars,"max_seeks_for_key (<= 100)"); } 
     402     
     403    # Query cache 
     404    if ($mysqlvermajor < 4) {  
     405        # For the sake of space, we will be quiet here 
     406        # MySQL versions < 4.01 don't support query caching 
     407        push(@generalrec,"Upgrade MySQL to version 4+ to utilize query caching"); 
     408    } elsif ($myvar{'query_cache_size'} < 1) { 
     409        badprint "Query cache is disabled\n"; 
     410        push(@incvars,"query_cache_size (>= 8M)"); 
     411    } elsif ($mystat{'Com_select'} == 0) { 
     412        badprint "Query cache cannot be analyzed - no SELECT statements executed\n"; 
     413    } else { 
     414        if ($mycalc{'query_cache_efficiency'} < 20) { 
     415            badprint "Query cache efficiency: $mycalc{'query_cache_efficiency'}%\n"; 
     416            push(@incvars,"query_cache_limit (> 1M, or use smaller result sets)"); 
     417        } else { 
     418            goodprint "Query cache efficiency: $mycalc{'query_cache_efficiency'}%\n"; 
     419        } 
     420        if ($mycalc{'query_cache_prunes_per_day'} > 98) { 
     421            badprint "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}\n"; 
     422            push(@incvars,"query_cache_size (> ".hr_bytes_rnd($myvar{'query_cache_size'}).")") 
     423        } else { 
     424            goodprint "Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}\n"; 
     425        } 
     426    } 
     427     
     428    # Sorting 
     429    if ($mycalc{'total_sorts'} == 0) { 
     430        # For the sake of space, we will be quiet here 
     431        # No sorts have run yet 
     432    } elsif ($mycalc{'pct_temp_sort_table'} > 10) { 
     433        badprint "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}%\n"; 
     434        push(@incvars,"sort_buffer_size (> ".hr_bytes_rnd($myvar{'sort_buffer_size'}).")"); 
     435        push(@incvars,"read_rnd_buffer_size (> ".hr_bytes_rnd($myvar{'read_rnd_buffer_size'}).")"); 
     436    } else { 
     437        goodprint "Sorts requiring temporary tables: $mycalc{'pct_temp_sort_table'}%\n"; 
     438    } 
     439     
     440    # Joins 
     441    if ($mycalc{'joins_without_indexes'} > 0) { 
     442        badprint "Joins performed without indexes: $mycalc{'joins_without_indexes'}\n"; 
     443        push(@incvars,"join_buffer_size (> ".hr_bytes($myvar{'join_buffer_size'}).", or always use indexes with joins)"); 
     444        push(@generalrec,"Adjust your join queries to always utilize indexes"); 
     445    } else { 
     446        # For the sake of space, we will be quiet here 
     447        # No joins have run without indexes 
     448    } 
     449     
     450    # Temporary tables 
     451    if ($mystat{'Created_tmp_tables'} > 0) { 
     452        if ($mycalc{'pct_temp_disk'} > 25) { 
     453            badprint "Temporary tables created on disk: $mycalc{'pct_temp_disk'}%\n"; 
     454            push(@incvars,"tmp_table_size (> ".hr_bytes_rnd($myvar{'tmp_table_size'}).")"); 
     455            push(@incvars,"max_heap_table_size (> ".hr_bytes_rnd($myvar{'max_heap_table_size'}).")"); 
     456            push(@generalrec,"Be sure that tmp_table_size/max_heap_table_size are equal"); 
     457        } else { 
     458            goodprint "Temporary tables created on disk: $mycalc{'pct_temp_disk'}%\n"; 
     459        } 
     460    } else { 
     461        # For the sake of space, we will be quiet here 
     462        # No temporary tables have been created 
     463    } 
     464 
     465    # Thread cache 
     466    if ($mycalc{'thread_cache_hit_rate'} <= 50) { 
     467        badprint "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}%\n"; 
     468    } else { 
     469        goodprint "Thread cache hit rate: $mycalc{'thread_cache_hit_rate'}%\n"; 
     470    } 
     471 
     472    # Table cache 
     473    if ($mystat{'Open_tables'} > 0) { 
     474        if ($mycalc{'table_cache_hit_rate'} < 20) { 
     475            badprint "Table cache hit rate: $mycalc{'table_cache_hit_rate'}%\n"; 
     476            push(@incvars,"table_cache (> ".$myvar{'table_cache'}.")"); 
     477            push(@generalrec,"Increase table_cache gradually to avoid file descriptor limits"); 
     478        } else { 
     479            goodprint "Table cache hit rate: $mycalc{'table_cache_hit_rate'}%\n"; 
     480        } 
     481    } 
     482 
     483    # Open files 
     484    if ($myvar{'open_files_limit'} > 0) { 
     485        if ($mycalc{'pct_files_open'} > 85) { 
     486            badprint "Open file limit used: $mycalc{'pct_files_open'}%\n"; 
     487            push(@incvars,"open_files_limit (> ".$myvar{'open_files_limit'}.")"); 
     488        } else { 
     489            goodprint "Open file limit used: $mycalc{'pct_files_open'}%\n"; 
     490        } 
     491    } 
     492 
     493    # Table locks 
     494    if (defined $mycalc{'pct_table_locks_immediate'}) { 
     495        if ($mycalc{'pct_table_locks_immediate'} < 95) { 
     496            badprint "Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}%\n"; 
     497            push(@generalrec,"Optimize queries and/or use InnoDB to reduce lock wait"); 
     498        } else { 
     499            goodprint "Table locks acquired immediately: $mycalc{'pct_table_locks_immediate'}%\n"; 
     500        } 
     501    } 
     502 
     503    # Performance options 
     504    if ($mysqlvermajor == 3 || ($mysqlvermajor == 4 && $mysqlverminor == 0)) { 
     505        push(@generalrec,"Upgrade to MySQL 4.1+ to use concurrent MyISAM inserts"); 
     506    } elsif ($myvar{'concurrent_insert'} eq "OFF" || $myvar{'concurrent_insert'} < 1) { 
     507        if ($mysqlvermajor == 4) { 
     508            push(@generalrec,"Enable concurrent_insert by setting it to 'ON'"); 
     509        } else { 
     510            push(@generalrec,"Enable concurrent_insert by setting it to 1"); 
     511        } 
     512    } 
     513
     514 
     515sub make_recommendations { 
     516    print "-------- Recommendations -----------------------------------------------------\n"; 
     517    if (@generalrec > 0) { 
     518        print "General recommendations:\n"; 
     519        foreach (@generalrec) { print "     ".$_."\n"; } 
     520    } 
     521    if (@incvars > 0) { 
     522        print "Variables to increase:\n"; 
     523        if ($mycalc{'pct_physical_memory'} > 85) { 
     524            print "     *** MySQL's maximum memory usage exceeds your installed memory ***\n". 
     525                  "     *** Add more RAM before increasing any MySQL buffer variables  ***\n"; 
     526        } 
     527        foreach (@incvars) { print "     ".$_."\n"; } 
     528    } 
     529    if (@decvars > 0) { 
     530        print "Variables to decrease:\n"; 
     531        foreach (@decvars) { print "     ".$_."\n"; } 
    633532    } 
    634533} 
     
    638537# --------------------------------------------------------------------------- 
    639538print   "     MySQL High-Performance Tuner - Major Hayden <major.hayden\@rackspace.com>\n". 
    640         "     Bug reports, feature requests, downloads at mysqltuner.com\n". 
     539        "     Bug reports, feature requests, and downloads at mysqltuner.com\n". 
    641540        "     Run with '--help' for additional options and output filtering\n"; 
    642 mysql_install_ok;               # Check to see if MySQL is installed 
    643 os_setup;                       # Set up some OS variables 
    644 setup_mysql_login;              # Gotta login first 
    645 get_all_vars;                   # Toss variables/status into hashes 
    646 validate_mysql_version;         # Check current MySQL version 
    647 mysql_initial_stats;            # Print some basic server stats 
    648 check_memory;                                   # Check memory usage 
    649 check_slow_queries;             # Check slow query percentage 
    650 check_connections;              # Check connection limits/usage 
    651 check_key_buffer;               # Check key buffer 
    652 check_query_cache;              # Check query caching 
    653 check_sort;                     # Check sorting buffers 
    654 check_join;                     # Check join buffers 
    655 check_temporary_tables;         # Check temporary table creation 
    656 check_other_buffers; 
    657 performance_options; 
     541os_setup;                           # Set up some OS variables 
     542mysql_setup;                        # Gotta login first 
     543get_all_vars;                       # Toss variables/status into hashes 
     544validate_mysql_version;             # Check current MySQL version 
     545calculations;                       # Calculate everything we need 
     546mysql_stats;                        # Print the server stats 
     547make_recommendations;               # Make recommendations based on stats 
    658548# --------------------------------------------------------------------------- 
    659549# END 'MAIN'