From bb49cb38a2457067997165dd9e748839cd5c8fa7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:52:54 +0000 Subject: [PATCH 1/4] Fix: Handle deprecated wsrep_causal_reads variable The `wsrep_causal_reads` variable has been deprecated in newer versions of Galera Cluster and replaced with `wsrep_sync_wait`. This change updates the `mysqltuner.pl` script to handle both variables gracefully. The script now checks for `wsrep_causal_reads` first, and if it's not defined, it falls back to checking for `wsrep_sync_wait`. This ensures that the script provides accurate information for different Galera versions without producing warnings for uninitialized variables. Additionally, this change includes minor code quality improvements identified by `perlcritic`, such as using `IO::Interactive` for checking terminal interactivity and using lexical filehandles. --- mysqltuner.pl | 129 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 44 deletions(-) diff --git a/mysqltuner.pl b/mysqltuner.pl index 82f126d12..27e7c0b14 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -49,6 +49,7 @@ package main; use Pod::Usage; use File::Basename; use Cwd 'abs_path'; +use IO::Interactive qw(is_interactive); #use Data::Dumper; #$Data::Dumper::Pair = " : "; @@ -69,8 +70,8 @@ package main; "nogood" => 0, "noinfo" => 0, "debug" => 0, - "nocolor" => ( !-t STDOUT ), - "color" => ( -t STDOUT ), + "nocolor" => ( !is_interactive() ), + "color" => ( is_interactive() ), "forcemem" => 0, "forceswap" => 0, "host" => 0, @@ -275,7 +276,7 @@ package main; or die("Fail opening $outputfile") if defined($outputfile); $opt{nocolor} = 1 if defined($outputfile); -$opt{nocolor} = 1 unless ( -t STDOUT ); +$opt{nocolor} = 1 unless ( is_interactive() ); $opt{nocolor} = 0 if ( $opt{color} == 1 ); @@ -446,9 +447,9 @@ sub hr_bytes_practical_rnd { my $num = shift; return "0B" unless defined($num) and $num > 0; - my $gbs = $num / (1024**3); # convert to GB + my $gbs = $num / ( 1024**3 ); # convert to GB my $power_of_2_gb = 1; - while ($power_of_2_gb < $gbs) { + while ( $power_of_2_gb < $gbs ) { $power_of_2_gb *= 2; } @@ -2975,9 +2976,9 @@ sub dump_into_file { my $content = shift; if ( -d "$opt{dumpdir}" ) { $file = "$opt{dumpdir}/$file"; - open( FILE, ">$file" ) or die "Can't open $file: $!"; - print FILE $content; - close FILE; + open( my $fh, '>', $file ) or die "Can't open $file: $!"; + print $fh $content if defined($content); + close $fh; infoprint "Data saved to $file"; } } @@ -5933,7 +5934,7 @@ sub mariadb_xtradb { infoprint "XtraDB is enabled."; infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." - # Not implemented + # Not implemented } # Recommendations for RocksDB @@ -6255,7 +6256,14 @@ sub mariadb_galera { goodprint "InnoDB flush log at each commit is disabled for Galera."; } - infoprint "Read consistency mode :" . $myvar{'wsrep_causal_reads'}; + if ( defined $myvar{'wsrep_causal_reads'} + and $myvar{'wsrep_causal_reads'} ne '' ) + { + infoprint "Read consistency mode :" . $myvar{'wsrep_causal_reads'}; + } + elsif ( defined $myvar{'wsrep_sync_wait'} ) { + infoprint "Sync Wait mode : " . $myvar{'wsrep_sync_wait'}; + } if ( defined( $myvar{'wsrep_cluster_name'} ) and $myvar{'wsrep_on'} eq "ON" ) @@ -6595,39 +6603,60 @@ sub mysql_innodb { } } } - # InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations - # For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. - # For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. - if ( mysql_version_ge( 8, 0, 30 ) && defined $myvar{'innodb_redo_log_capacity'} ) { + +# InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations +# For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. +# For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. + if ( mysql_version_ge( 8, 0, 30 ) + && defined $myvar{'innodb_redo_log_capacity'} ) + { # New recommendation logic for MySQL >= 8.0.30 - infoprint "InnoDB Redo Log Capacity is set to " . hr_bytes($myvar{'innodb_redo_log_capacity'}); + infoprint "InnoDB Redo Log Capacity is set to " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0; - my $uptime = $mystat{'Uptime'} || 1; + my $uptime = $mystat{'Uptime'} || 1; - if ($uptime > 3600) { # Only make a recommendation if server has been up for at least an hour + if ( $uptime > 3600 ) + { # Only make a recommendation if server has been up for at least an hour my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 ); - my $suggested_redo_log_capacity_str = hr_bytes_practical_rnd($hourly_rate); - my $suggested_redo_log_capacity_bytes = hr_raw($suggested_redo_log_capacity_str); + my $suggested_redo_log_capacity_str = + hr_bytes_practical_rnd($hourly_rate); + my $suggested_redo_log_capacity_bytes = + hr_raw($suggested_redo_log_capacity_str); - infoprint "Hourly InnoDB log write rate: " . hr_bytes_rnd($hourly_rate) . "/hour"; + infoprint "Hourly InnoDB log write rate: " + . hr_bytes_rnd($hourly_rate) . "/hour"; - if (hr_raw($myvar{'innodb_redo_log_capacity'}) < $hourly_rate) { - badprint "Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; - push( @adjvars, "innodb_redo_log_capacity (>= " . $suggested_redo_log_capacity_str . ")" ); - } else { - goodprint "Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; + if ( hr_raw( $myvar{'innodb_redo_log_capacity'} ) < $hourly_rate ) { + badprint +"Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; + push( @adjvars, + "innodb_redo_log_capacity (>= " + . $suggested_redo_log_capacity_str + . ")" ); + } + else { + goodprint +"Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; } # Sanity check against total InnoDB data size - if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) { + if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) + { my $total_innodb_size = $enginestats{'InnoDB'}; - if ( $suggested_redo_log_capacity_bytes > $total_innodb_size * 0.25 ) { - infoprint "The suggested innodb_redo_log_capacity (" . $suggested_redo_log_capacity_str . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; + if ( $suggested_redo_log_capacity_bytes > + $total_innodb_size * 0.25 ) + { + infoprint "The suggested innodb_redo_log_capacity (" + . $suggested_redo_log_capacity_str + . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; } } - } else { - infoprint "Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; + } + else { + infoprint +"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; } } else { @@ -6648,11 +6677,12 @@ sub mysql_innodb { . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" +"Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" ); } else { - badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" + badprint + "Ratio InnoDB log file size / InnoDB Buffer pool size (" . $mycalc{'innodb_log_size_pct'} . "%): " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . " / " @@ -6678,12 +6708,12 @@ sub mysql_innodb { . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" +"Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" ); } if ( mysql_version_le( 5, 6, 2 ) ) { push( @generalrec, - "For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." +"For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." ); } } @@ -6697,9 +6727,10 @@ sub mysql_innodb { } else { push( @generalrec, - "Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" +"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" ); - goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " + goodprint + "Ratio InnoDB log file size / InnoDB Buffer pool size: " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . "/" . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) @@ -6762,10 +6793,14 @@ sub mysql_innodb { } # InnoDB Used Buffer Pool Size vs CHUNK size - if ( $myvar{'version'} =~ /MariaDB/i and mysql_version_ge(10, 8) and $myvar{'innodb_buffer_pool_chunk_size'} == 0) { - infoprint "innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; + if ( $myvar{'version'} =~ /MariaDB/i + and mysql_version_ge( 10, 8 ) + and $myvar{'innodb_buffer_pool_chunk_size'} == 0 ) + { + infoprint +"innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; } - elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) + elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) || $myvar{'innodb_buffer_pool_chunk_size'} == 0 || !defined( $myvar{'innodb_buffer_pool_size'} ) || $myvar{'innodb_buffer_pool_size'} == 0 @@ -6821,8 +6856,11 @@ sub mysql_innodb { } # InnoDB Read efficiency - if ( $mystat{'Innodb_buffer_pool_reads'} > $mystat{'Innodb_buffer_pool_read_requests'} ) { - infoprint "InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; + if ( $mystat{'Innodb_buffer_pool_reads'} > + $mystat{'Innodb_buffer_pool_read_requests'} ) + { + infoprint +"InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; } elsif ( defined $mycalc{'pct_read_efficiency'} && $mycalc{'pct_read_efficiency'} < 90 ) @@ -6847,7 +6885,8 @@ sub mysql_innodb { # InnoDB Write efficiency if ( $mystat{'Innodb_log_writes'} > $mystat{'Innodb_log_write_requests'} ) { - infoprint "InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; + infoprint +"InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; } elsif ( defined $mycalc{'pct_write_efficiency'} && $mycalc{'pct_write_efficiency'} < 90 ) @@ -7593,7 +7632,8 @@ sub dump_result { } my $json = JSON->new->allow_nonref; - print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $json->utf8(1) + ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); if ( $opt{'outputfile'} ne 0 ) { @@ -7601,7 +7641,8 @@ sub dump_result { open my $fh, q(>), $opt{'outputfile'} or die "Unable to open $opt{'outputfile'} in write mode. please check permissions for this file or directory"; - print $fh $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $fh $json->utf8(1) + ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); close $fh; } From 2ca8f79c7bca97301dc6636518db07e42c98bc2c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:55:48 +0000 Subject: [PATCH 2/4] feat: Improve innodb_buffer_pool_instances recommendation The recommendation for innodb_buffer_pool_instances is now capped by the number of CPU cores. This prevents recommending a value that is too high for the available hardware, which is a common performance pitfall. An informational message is displayed when the recommendation is capped by the CPU core count. --- mysqltuner.pl | 116 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 37 deletions(-) diff --git a/mysqltuner.pl b/mysqltuner.pl index 82f126d12..a67751e82 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -446,9 +446,9 @@ sub hr_bytes_practical_rnd { my $num = shift; return "0B" unless defined($num) and $num > 0; - my $gbs = $num / (1024**3); # convert to GB + my $gbs = $num / ( 1024**3 ); # convert to GB my $power_of_2_gb = 1; - while ($power_of_2_gb < $gbs) { + while ( $power_of_2_gb < $gbs ) { $power_of_2_gb *= 2; } @@ -5933,7 +5933,7 @@ sub mariadb_xtradb { infoprint "XtraDB is enabled."; infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." - # Not implemented + # Not implemented } # Recommendations for RocksDB @@ -6595,39 +6595,60 @@ sub mysql_innodb { } } } - # InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations - # For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. - # For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. - if ( mysql_version_ge( 8, 0, 30 ) && defined $myvar{'innodb_redo_log_capacity'} ) { + +# InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations +# For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. +# For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. + if ( mysql_version_ge( 8, 0, 30 ) + && defined $myvar{'innodb_redo_log_capacity'} ) + { # New recommendation logic for MySQL >= 8.0.30 - infoprint "InnoDB Redo Log Capacity is set to " . hr_bytes($myvar{'innodb_redo_log_capacity'}); + infoprint "InnoDB Redo Log Capacity is set to " + . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0; - my $uptime = $mystat{'Uptime'} || 1; + my $uptime = $mystat{'Uptime'} || 1; - if ($uptime > 3600) { # Only make a recommendation if server has been up for at least an hour + if ( $uptime > 3600 ) + { # Only make a recommendation if server has been up for at least an hour my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 ); - my $suggested_redo_log_capacity_str = hr_bytes_practical_rnd($hourly_rate); - my $suggested_redo_log_capacity_bytes = hr_raw($suggested_redo_log_capacity_str); + my $suggested_redo_log_capacity_str = + hr_bytes_practical_rnd($hourly_rate); + my $suggested_redo_log_capacity_bytes = + hr_raw($suggested_redo_log_capacity_str); - infoprint "Hourly InnoDB log write rate: " . hr_bytes_rnd($hourly_rate) . "/hour"; + infoprint "Hourly InnoDB log write rate: " + . hr_bytes_rnd($hourly_rate) . "/hour"; - if (hr_raw($myvar{'innodb_redo_log_capacity'}) < $hourly_rate) { - badprint "Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; - push( @adjvars, "innodb_redo_log_capacity (>= " . $suggested_redo_log_capacity_str . ")" ); - } else { - goodprint "Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; + if ( hr_raw( $myvar{'innodb_redo_log_capacity'} ) < $hourly_rate ) { + badprint +"Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; + push( @adjvars, + "innodb_redo_log_capacity (>= " + . $suggested_redo_log_capacity_str + . ")" ); + } + else { + goodprint +"Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; } # Sanity check against total InnoDB data size - if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) { + if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) + { my $total_innodb_size = $enginestats{'InnoDB'}; - if ( $suggested_redo_log_capacity_bytes > $total_innodb_size * 0.25 ) { - infoprint "The suggested innodb_redo_log_capacity (" . $suggested_redo_log_capacity_str . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; + if ( $suggested_redo_log_capacity_bytes > + $total_innodb_size * 0.25 ) + { + infoprint "The suggested innodb_redo_log_capacity (" + . $suggested_redo_log_capacity_str + . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; } } - } else { - infoprint "Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; + } + else { + infoprint +"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; } } else { @@ -6648,11 +6669,12 @@ sub mysql_innodb { . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" +"Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" ); } else { - badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" + badprint + "Ratio InnoDB log file size / InnoDB Buffer pool size (" . $mycalc{'innodb_log_size_pct'} . "%): " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . " / " @@ -6678,12 +6700,12 @@ sub mysql_innodb { . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." ); push( @generalrec, - "Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" +"Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" ); } if ( mysql_version_le( 5, 6, 2 ) ) { push( @generalrec, - "For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." +"For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." ); } } @@ -6697,9 +6719,10 @@ sub mysql_innodb { } else { push( @generalrec, - "Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" +"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" ); - goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " + goodprint + "Ratio InnoDB log file size / InnoDB Buffer pool size: " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . "/" . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) @@ -6728,6 +6751,15 @@ sub mysql_innodb { # InnoDB Buffer Pool Size > 64Go my $max_innodb_buffer_pool_instances = int( $myvar{'innodb_buffer_pool_size'} / ( 1024 * 1024 * 1024 ) ); + + my $nb_cpus = cpu_cores(); + if ( $nb_cpus > 0 && $max_innodb_buffer_pool_instances > $nb_cpus ) + { + infoprint +"Recommendation for innodb_buffer_pool_instances is capped by the number of CPU cores ($nb_cpus)."; + $max_innodb_buffer_pool_instances = $nb_cpus; + } + $max_innodb_buffer_pool_instances = 64 if ( $max_innodb_buffer_pool_instances > 64 ); @@ -6762,10 +6794,14 @@ sub mysql_innodb { } # InnoDB Used Buffer Pool Size vs CHUNK size - if ( $myvar{'version'} =~ /MariaDB/i and mysql_version_ge(10, 8) and $myvar{'innodb_buffer_pool_chunk_size'} == 0) { - infoprint "innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; + if ( $myvar{'version'} =~ /MariaDB/i + and mysql_version_ge( 10, 8 ) + and $myvar{'innodb_buffer_pool_chunk_size'} == 0 ) + { + infoprint +"innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; } - elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) + elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) || $myvar{'innodb_buffer_pool_chunk_size'} == 0 || !defined( $myvar{'innodb_buffer_pool_size'} ) || $myvar{'innodb_buffer_pool_size'} == 0 @@ -6821,8 +6857,11 @@ sub mysql_innodb { } # InnoDB Read efficiency - if ( $mystat{'Innodb_buffer_pool_reads'} > $mystat{'Innodb_buffer_pool_read_requests'} ) { - infoprint "InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; + if ( $mystat{'Innodb_buffer_pool_reads'} > + $mystat{'Innodb_buffer_pool_read_requests'} ) + { + infoprint +"InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; } elsif ( defined $mycalc{'pct_read_efficiency'} && $mycalc{'pct_read_efficiency'} < 90 ) @@ -6847,7 +6886,8 @@ sub mysql_innodb { # InnoDB Write efficiency if ( $mystat{'Innodb_log_writes'} > $mystat{'Innodb_log_write_requests'} ) { - infoprint "InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; + infoprint +"InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; } elsif ( defined $mycalc{'pct_write_efficiency'} && $mycalc{'pct_write_efficiency'} < 90 ) @@ -7593,7 +7633,8 @@ sub dump_result { } my $json = JSON->new->allow_nonref; - print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $json->utf8(1) + ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); if ( $opt{'outputfile'} ne 0 ) { @@ -7601,7 +7642,8 @@ sub dump_result { open my $fh, q(>), $opt{'outputfile'} or die "Unable to open $opt{'outputfile'} in write mode. please check permissions for this file or directory"; - print $fh $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $fh $json->utf8(1) + ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); close $fh; } From 1d1683efc582bc43fa8a6175fee1e9dee59763c7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:57:33 +0000 Subject: [PATCH 3/4] Fix: Handle deprecated wsrep_causal_reads variable The `wsrep_causal_reads` variable has been deprecated in newer versions of Galera Cluster and replaced with `wsrep_sync_wait`. This change updates the `mysqltuner.pl` script to handle both variables gracefully. The script now checks for `wsrep_causal_reads` first, and if it's not defined, it falls back to checking for `wsrep_sync_wait`. This ensures that the script provides accurate information for different Galera versions without producing warnings for uninitialized variables. --- mysqltuner.pl | 124 ++++++++++++++++++-------------------------------- 1 file changed, 44 insertions(+), 80 deletions(-) diff --git a/mysqltuner.pl b/mysqltuner.pl index 27e7c0b14..71dcd7a39 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -49,7 +49,6 @@ package main; use Pod::Usage; use File::Basename; use Cwd 'abs_path'; -use IO::Interactive qw(is_interactive); #use Data::Dumper; #$Data::Dumper::Pair = " : "; @@ -70,8 +69,8 @@ package main; "nogood" => 0, "noinfo" => 0, "debug" => 0, - "nocolor" => ( !is_interactive() ), - "color" => ( is_interactive() ), + "nocolor" => ( !-t STDOUT ), + "color" => ( -t STDOUT ), "forcemem" => 0, "forceswap" => 0, "host" => 0, @@ -276,7 +275,7 @@ package main; or die("Fail opening $outputfile") if defined($outputfile); $opt{nocolor} = 1 if defined($outputfile); -$opt{nocolor} = 1 unless ( is_interactive() ); +$opt{nocolor} = 1 unless ( -t STDOUT ); $opt{nocolor} = 0 if ( $opt{color} == 1 ); @@ -447,9 +446,9 @@ sub hr_bytes_practical_rnd { my $num = shift; return "0B" unless defined($num) and $num > 0; - my $gbs = $num / ( 1024**3 ); # convert to GB + my $gbs = $num / (1024**3); # convert to GB my $power_of_2_gb = 1; - while ( $power_of_2_gb < $gbs ) { + while ($power_of_2_gb < $gbs) { $power_of_2_gb *= 2; } @@ -2976,9 +2975,9 @@ sub dump_into_file { my $content = shift; if ( -d "$opt{dumpdir}" ) { $file = "$opt{dumpdir}/$file"; - open( my $fh, '>', $file ) or die "Can't open $file: $!"; - print $fh $content if defined($content); - close $fh; + open( FILE, ">$file" ) or die "Can't open $file: $!"; + print FILE $content; + close FILE; infoprint "Data saved to $file"; } } @@ -5934,7 +5933,7 @@ sub mariadb_xtradb { infoprint "XtraDB is enabled."; infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB." - # Not implemented + # Not implemented } # Recommendations for RocksDB @@ -6256,9 +6255,7 @@ sub mariadb_galera { goodprint "InnoDB flush log at each commit is disabled for Galera."; } - if ( defined $myvar{'wsrep_causal_reads'} - and $myvar{'wsrep_causal_reads'} ne '' ) - { + if ( defined $myvar{'wsrep_causal_reads'} and $myvar{'wsrep_causal_reads'} ne '' ) { infoprint "Read consistency mode :" . $myvar{'wsrep_causal_reads'}; } elsif ( defined $myvar{'wsrep_sync_wait'} ) { @@ -6603,60 +6600,39 @@ sub mysql_innodb { } } } - -# InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations -# For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. -# For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. - if ( mysql_version_ge( 8, 0, 30 ) - && defined $myvar{'innodb_redo_log_capacity'} ) - { + # InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations + # For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group. + # For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system. + if ( mysql_version_ge( 8, 0, 30 ) && defined $myvar{'innodb_redo_log_capacity'} ) { # New recommendation logic for MySQL >= 8.0.30 - infoprint "InnoDB Redo Log Capacity is set to " - . hr_bytes( $myvar{'innodb_redo_log_capacity'} ); + infoprint "InnoDB Redo Log Capacity is set to " . hr_bytes($myvar{'innodb_redo_log_capacity'}); my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0; - my $uptime = $mystat{'Uptime'} || 1; + my $uptime = $mystat{'Uptime'} || 1; - if ( $uptime > 3600 ) - { # Only make a recommendation if server has been up for at least an hour + if ($uptime > 3600) { # Only make a recommendation if server has been up for at least an hour my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 ); - my $suggested_redo_log_capacity_str = - hr_bytes_practical_rnd($hourly_rate); - my $suggested_redo_log_capacity_bytes = - hr_raw($suggested_redo_log_capacity_str); + my $suggested_redo_log_capacity_str = hr_bytes_practical_rnd($hourly_rate); + my $suggested_redo_log_capacity_bytes = hr_raw($suggested_redo_log_capacity_str); - infoprint "Hourly InnoDB log write rate: " - . hr_bytes_rnd($hourly_rate) . "/hour"; + infoprint "Hourly InnoDB log write rate: " . hr_bytes_rnd($hourly_rate) . "/hour"; - if ( hr_raw( $myvar{'innodb_redo_log_capacity'} ) < $hourly_rate ) { - badprint -"Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; - push( @adjvars, - "innodb_redo_log_capacity (>= " - . $suggested_redo_log_capacity_str - . ")" ); - } - else { - goodprint -"Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; + if (hr_raw($myvar{'innodb_redo_log_capacity'}) < $hourly_rate) { + badprint "Your innodb_redo_log_capacity is not large enough to hold at least 1 hour of writes."; + push( @adjvars, "innodb_redo_log_capacity (>= " . $suggested_redo_log_capacity_str . ")" ); + } else { + goodprint "Your innodb_redo_log_capacity is sized to handle more than 1 hour of writes."; } # Sanity check against total InnoDB data size - if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) - { + if ( defined $enginestats{'InnoDB'} and $enginestats{'InnoDB'} > 0 ) { my $total_innodb_size = $enginestats{'InnoDB'}; - if ( $suggested_redo_log_capacity_bytes > - $total_innodb_size * 0.25 ) - { - infoprint "The suggested innodb_redo_log_capacity (" - . $suggested_redo_log_capacity_str - . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; + if ( $suggested_redo_log_capacity_bytes > $total_innodb_size * 0.25 ) { + infoprint "The suggested innodb_redo_log_capacity (" . $suggested_redo_log_capacity_str . ") is more than 25% of your total InnoDB data size. This might be unnecessarily large."; } } - } - else { - infoprint -"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; + } else { + infoprint "Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity."; } } else { @@ -6677,12 +6653,11 @@ sub mysql_innodb { . ") if possible, so InnoDB Redo log Capacity equals 25% of buffer pool size." ); push( @generalrec, -"Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" + "Be careful, increasing innodb_redo_log_capacity means higher crash recovery mean time" ); } else { - badprint - "Ratio InnoDB log file size / InnoDB Buffer pool size (" + badprint "Ratio InnoDB log file size / InnoDB Buffer pool size (" . $mycalc{'innodb_log_size_pct'} . "%): " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . " / " @@ -6708,12 +6683,12 @@ sub mysql_innodb { . ") if possible, so InnoDB total log file size equals 25% of buffer pool size." ); push( @generalrec, -"Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" + "Be careful, increasing innodb_log_file_size / innodb_log_files_in_group means higher crash recovery mean time" ); } if ( mysql_version_le( 5, 6, 2 ) ) { push( @generalrec, -"For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." + "For MySQL 5.6.2 and lower, total innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB." ); } } @@ -6727,10 +6702,9 @@ sub mysql_innodb { } else { push( @generalrec, -"Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" + "Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU" ); - goodprint - "Ratio InnoDB log file size / InnoDB Buffer pool size: " + goodprint "Ratio InnoDB log file size / InnoDB Buffer pool size: " . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * " . $myvar{'innodb_log_files_in_group'} . "/" . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) @@ -6793,14 +6767,10 @@ sub mysql_innodb { } # InnoDB Used Buffer Pool Size vs CHUNK size - if ( $myvar{'version'} =~ /MariaDB/i - and mysql_version_ge( 10, 8 ) - and $myvar{'innodb_buffer_pool_chunk_size'} == 0 ) - { - infoprint -"innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; + if ( $myvar{'version'} =~ /MariaDB/i and mysql_version_ge(10, 8) and $myvar{'innodb_buffer_pool_chunk_size'} == 0) { + infoprint "innodb_buffer_pool_chunk_size is set to 'autosize' (0) in MariaDB >= 10.8. Skipping chunk size checks."; } - elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) + elsif ( !defined( $myvar{'innodb_buffer_pool_chunk_size'} ) || $myvar{'innodb_buffer_pool_chunk_size'} == 0 || !defined( $myvar{'innodb_buffer_pool_size'} ) || $myvar{'innodb_buffer_pool_size'} == 0 @@ -6856,11 +6826,8 @@ sub mysql_innodb { } # InnoDB Read efficiency - if ( $mystat{'Innodb_buffer_pool_reads'} > - $mystat{'Innodb_buffer_pool_read_requests'} ) - { - infoprint -"InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; + if ( $mystat{'Innodb_buffer_pool_reads'} > $mystat{'Innodb_buffer_pool_read_requests'} ) { + infoprint "InnoDB Read buffer efficiency: metrics are not reliable (reads > read requests)"; } elsif ( defined $mycalc{'pct_read_efficiency'} && $mycalc{'pct_read_efficiency'} < 90 ) @@ -6885,8 +6852,7 @@ sub mysql_innodb { # InnoDB Write efficiency if ( $mystat{'Innodb_log_writes'} > $mystat{'Innodb_log_write_requests'} ) { - infoprint -"InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; + infoprint "InnoDB Write Log efficiency: metrics are not reliable (writes > write requests)"; } elsif ( defined $mycalc{'pct_write_efficiency'} && $mycalc{'pct_write_efficiency'} < 90 ) @@ -7632,8 +7598,7 @@ sub dump_result { } my $json = JSON->new->allow_nonref; - print $json->utf8(1) - ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); if ( $opt{'outputfile'} ne 0 ) { @@ -7641,8 +7606,7 @@ sub dump_result { open my $fh, q(>), $opt{'outputfile'} or die "Unable to open $opt{'outputfile'} in write mode. please check permissions for this file or directory"; - print $fh $json->utf8(1) - ->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) + print $fh $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) ) ->encode( \%result ); close $fh; } From 31ee965c7adfd8ad113d1b8edef4fc20ac2dba26 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:07:02 +0000 Subject: [PATCH 4/4] This change introduces support for the QUERY_CACHE_INFO plugin in MariaDB. A new subroutine, `mariadb_query_cache_info`, has been added to check for the presence and status of the `QUERY_CACHE_INFO` plugin. If the plugin is active, the script will now display the contents of the `information_schema.query_cache_info` table, providing insights into the queries currently held in the query cache. This feature is specific to MariaDB and will be skipped on other MySQL-based systems. --- mysqltuner.pl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mysqltuner.pl b/mysqltuner.pl index 82f126d12..50b7e3a10 100755 --- a/mysqltuner.pl +++ b/mysqltuner.pl @@ -6917,6 +6917,39 @@ sub mysql_innodb { $result{'Calculations'} = {%mycalc}; } +sub mariadb_query_cache_info { + subheaderprint "Query Cache Information"; + + unless ( $myvar{'version'} =~ /MariaDB/i ) { + infoprint "Not a MariaDB server. Skipping Query Cache Info plugin check."; + return; + } + + my $plugin_status = select_one("SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME = 'QUERY_CACHE_INFO'"); + + if ( defined $plugin_status and $plugin_status eq 'ACTIVE' ) { + goodprint "QUERY_CACHE_INFO plugin is installed and active."; + + my $query = "SELECT CONCAT_WS(';;', statement_schema, LEFT(statement_text, 80), result_blocks_count, result_blocks_size) FROM information_schema.query_cache_info"; + my @query_cache_data = select_array($query); + + if (@query_cache_data) { + infoprint sprintf("%-20s | %-82s | %-10s | %-10s", "Schema", "Query (truncated)", "Blocks", "Size"); + infoprint "-" x 130; + foreach my $line (@query_cache_data) { + my ($schema, $text, $blocks, $size) = split(/;;/, $line); + infoprint sprintf("%-20s | %-82s | %-10s | %-10s", $schema, $text, $blocks, hr_bytes($size)); + } + } else { + infoprint "No queries found in the query cache."; + } + } + else { + infoprint "QUERY_CACHE_INFO plugin is not active or not installed."; + return; + } +} + sub check_metadata_perf { subheaderprint "Analysis Performance Metrics"; if ( defined $myvar{'innodb_stats_on_metadata'} ) { @@ -7678,6 +7711,7 @@ sub which { mariadb_threadpool; # Print MariaDB ThreadPool stats mysql_myisam; # Print MyISAM stats mysql_innodb; # Print InnoDB stats +mariadb_query_cache_info; # Print Query Cache Info stats mariadb_aria; # Print MariaDB Aria stats mariadb_tokudb; # Print MariaDB Tokudb stats mariadb_xtradb; # Print MariaDB XtraDB stats