',
- cat => 'MISC'
- },
- 'bannedports' => {
- type => '=s',
- default => '0',
- desc => 'Ports banned separated by comma',
- placeholder => '',
- cat => 'MISC'
- },
- 'maxportallowed' => {
- type => '=i',
- default => 0,
- desc => 'Number of open ports allowable',
- placeholder => '',
- cat => 'MISC'
- },
- 'defaultarch' => {
- type => '=i',
- default => 64,
- desc => 'Default architecture (32 or 64)',
- placeholder => '<32|64>',
- cat => 'MISC',
- validate => sub { $_[0] == 32 || $_[0] == 64 }
- },
- 'noask' => {
- type => '!',
- default => 0,
- desc => "Don't ask for confirmation",
- cat => 'MISC'
- },
- 'help|?' => {
- type => '',
- default => 0,
- desc => 'Show this help message',
- cat => 'MISC'
- },
-);
-
-# Initialize %opt from metadata
-our %opt = map {
- my ($primary) = split /\|/, $_;
- $primary =~ s/[!+=:].*$//; # Strip modifiers (Getopt::Long compatibility)
- $primary => $CLI_METADATA{$_}->{default}
-} keys %CLI_METADATA;
-
-# Declare shared variables at top level
-our (
- $devnull, $basic_password_files, $outputfile,
- $fh, $me, $good,
- $bad, $info, $deb,
- $cmd, $end, $maxlines,
- $mysqlvermajor, $mysqlverminor, $mysqlvermicro,
- @banned_ports, @dblist, %result
-);
-
-# Gather the options from the command line
-sub parse_cli_args {
-
- # Build GetOptions arguments dynamically
- my @getopt_args;
- Getopt::Long::Configure( "no_auto_abbrev", "no_ignore_case" );
- foreach my $opt_spec ( sort keys %CLI_METADATA ) {
- my $type = $CLI_METADATA{$opt_spec}->{type} // '';
- my ($primary) = split /\|/, $opt_spec;
- $primary =~ s/[!+=:].*$//; # Strip modifiers
- my $final_spec = $opt_spec;
-
- # Only append type if it's not already part of the specification key
- if ( $type && $opt_spec !~ /\Q$type\E$/ ) {
- $final_spec .= $type;
- }
- push @getopt_args, $final_spec => \$opt{$primary};
- }
-
- GetOptions(@getopt_args)
- or pod2usage(
- -exitval => 1,
- -verbose => 99,
- -sections =>
- [ "NAME", "IMPORTANT USAGE GUIDELINES", "OPTIONS", "VERSION" ]
- );
-
- # Apply metadata-driven rules (Implications and Validation)
- foreach my $opt_spec ( keys %CLI_METADATA ) {
- my ($primary) = split /\|/, $opt_spec;
- my $meta = $CLI_METADATA{$opt_spec};
-
- # Implications (e.g., --feature implies --verbose)
- if ( ( $opt{$primary} // '0' ) ne '0' && $meta->{implies} ) {
- while ( my ( $target, $value ) = each %{ $meta->{implies} } ) {
- $opt{$target} = $value;
- }
- }
-
- # Validation (regex or coderef)
- if ( defined $opt{$primary}
- && ( $opt{$primary} // '0' ) ne '0'
- && $meta->{validate} )
- {
- my $val = $opt{$primary};
- my $is_valid = 1;
- if ( ref $meta->{validate} eq 'Regexp' ) {
- $is_valid = ( $val =~ $meta->{validate} );
- }
- elsif ( ref $meta->{validate} eq 'CODE' ) {
- $is_valid = $meta->{validate}->($val);
- }
-
- unless ($is_valid) {
- print "Error: Invalid value for --$primary: $val\n";
- exit 1;
- }
- }
- }
-}
-
-sub setup_environment {
- if ( defined $opt{'help'} && $opt{'help'} == 1 ) {
- show_help();
- }
-
- if ( defined $opt{'version'} && $opt{'version'} == 1 ) {
- subheaderprint("MySQLTuner $tunerversion");
- exit(0);
- }
-
- $devnull = File::Spec->devnull();
- $basic_password_files =
- ( $opt{passwordfile} eq "0" )
- ? abs_path( dirname(__FILE__) ) . "/basic_passwords.txt"
- : abs_path( $opt{passwordfile} );
-
- # Username from envvar
- if ( exists $opt{userenv} && exists $ENV{ $opt{userenv} // '' } ) {
- $opt{user} = $ENV{ $opt{userenv} // '' };
- }
-
- # Related to password option
- if ( exists $opt{passenv} && exists $ENV{ $opt{userenv} // '' } ) {
- $opt{pass} = $ENV{ $opt{userenv} // '' };
- }
- $opt{pass} = $opt{password}
- if ( ( $opt{pass} // '0' ) eq '0' and ( $opt{password} // '0' ) ne '0' );
-
- # for RPM distributions
- $basic_password_files = "/usr/share/mysqltuner/basic_passwords.txt"
- unless -f "$basic_password_files";
-
- $opt{dbgpattern} = '.*' if ( ( $opt{dbgpattern} // '' ) eq '' );
-
- # check if we need to enable verbose mode
- $opt{noprettyicon} = 0 if ( $opt{noprettyicon} // 0 ) != 1;
- $opt{nocolor} = 1 if defined( $opt{outputfile} );
- $opt{noprocess} = 0
- if ( ( $opt{noprocess} // 0 ) == 1 ); # Don't print process information
- $opt{structstat} = 0
- if ( ( $opt{nostructstat} // 0 ) == 1 )
- ; # Don't print table struct information
- $opt{myisamstat} = 1
- if ( not defined( $opt{myisamstat} ) );
- $opt{myisamstat} = 0
- if ( ( $opt{nomyisamstat} // 0 ) == 1 )
- ; # Don't print MyISAM table information
-
- # Handle cvefile if it was not passed but exists locally
- $opt{cvefile} = './vulnerabilities.csv'
- if ( ( !defined( $opt{cvefile} ) || $opt{cvefile} eq '' )
- && -f './vulnerabilities.csv' );
-
- $opt{'bannedports'} = '' unless defined( $opt{'bannedports'} );
- @banned_ports = split ',', $opt{'bannedports'};
-
- $outputfile = undef;
- $outputfile = abs_path( $opt{outputfile} ) unless $opt{outputfile} eq "0";
-
- $fh = undef;
- open( $fh, '>', $outputfile )
- or die("Fail opening $outputfile")
- if defined($outputfile);
- $opt{nocolor} = 1 if defined($outputfile);
- $opt{nocolor} = 1 unless ( -t STDOUT );
-
- $opt{nocolor} = 0 if ( ( $opt{color} // 0 ) == 1 );
-
- # Setting up the colors for the print styles
- $me = ( getpwuid($<) )[0] // $ENV{USER} // $ENV{USERNAME} // 'unknown';
-
- if ($is_win) { $opt{nocolor} = 1; }
- $good = ( $opt{nocolor} == 0 ) ? "[\e[0;32mOK\e[0m]" : "[OK]";
- $bad = ( $opt{nocolor} == 0 ) ? "[\e[0;31m!!\e[0m]" : "[!!]";
- $info = ( $opt{nocolor} == 0 ) ? "[\e[0;34m--\e[0m]" : "[--]";
- $deb = ( $opt{nocolor} == 0 ) ? "[\e[0;31mDG\e[0m]" : "[DG]";
- $cmd = ( $opt{nocolor} == 0 ) ? "\e[1;32m[CMD]($me)" : "[CMD]($me)";
- $end = ( $opt{nocolor} == 0 ) ? "\e[0m" : "";
-
- if ( ( not $is_win ) and ( $opt{noprettyicon} == 0 ) ) {
- $good = ( $opt{nocolor} == 0 ) ? "\e[0;32m✔\e[0m " : "✔ ";
- $bad = ( $opt{nocolor} == 0 ) ? "\e[0;31m✘\e[0m " : "✘ ";
- $info = ( $opt{nocolor} == 0 ) ? "\e[0;34mℹ\e[0m " : "ℹ ";
- $deb = ( $opt{nocolor} == 0 ) ? "\e[0;31m⚙\e[0m " : "⚙ ";
- $cmd = ( $opt{nocolor} == 0 ) ? "\e[1;32m⌨️($me)" : "⌨️($me)";
- $end = ( $opt{nocolor} == 0 ) ? "\e[0m " : " ";
- }
-
- # Maximum lines of log output to read from end
- $maxlines = 30000;
-
- # Super structure containing all information
- %result = ();
- $result{'MySQLTuner'}{'version'} = $tunerversion;
- $result{'MySQLTuner'}{'datetime'} = scalar localtime;
- $result{'MySQLTuner'}{'options'} = \%opt;
-}
-
-# Functions that handle the print styles
-sub show_help {
- my %categories = (
- 'CONNECTION' => 'CONNECTION AND AUTHENTICATION',
- 'CLOUD' => 'CLOUD SUPPORT',
- 'PERFORMANCE' => 'PERFORMANCE AND REPORTING OPTIONS',
- 'OUTPUT' => 'OUTPUT OPTIONS',
- 'MISC' => 'MISCELLANEOUS OPTIONS',
- );
-
- print "MySQLTuner $tunerversion - MySQL High Performance Tuning Script\n\n";
- print "Usage: ./mysqltuner.pl [options]\n\n";
-
- foreach my $cat (qw(CONNECTION CLOUD PERFORMANCE OUTPUT MISC)) {
- print "$categories{$cat}:\n";
- foreach my $opt_spec ( sort keys %CLI_METADATA ) {
- next unless $CLI_METADATA{$opt_spec}->{cat} eq $cat;
- my $meta = $CLI_METADATA{$opt_spec};
- my ($primary) = split /\|/, $opt_spec;
- my $display = "--$primary";
-
- # Handle negatable options (trailing !)
- my $is_negatable =
- ( $opt_spec =~ /!$/ || ( $meta->{type} // '' ) eq '!' );
- if ($is_negatable) {
-
- # Remove ! from display if present
- $display =~ s/!$//;
- }
-
- $display .= " " . $meta->{placeholder} if $meta->{placeholder};
-
- # Handle aliases
- my @parts = split /\|/, $opt_spec;
- shift @parts; # remove primary
-
- my @display_parts;
- if ($is_negatable) {
- push @display_parts, "no-$primary";
- }
- push @display_parts, @parts;
-
- if (@display_parts) {
-
- # Format aliases: length 1 -> -x, length > 1 -> --xx
- $display .= " (" . join(
- ", ",
- map {
- my $p = $_;
- $p =~ s/!$//;
- length($p) == 1 ? "-$p" : "--$p"
- } @display_parts
- ) . ")";
- }
-
- printf " %-32s %s", $display, $meta->{desc};
-
- # Special case for defaults: Don't print if 0 or empty string unless meaningful
- # For booleans (type !), default 0 is expected and silent.
- if ( defined $meta->{default}
- && $meta->{default} ne '0'
- && $meta->{default} ne ''
- && $meta->{default} ne "0" )
- {
-# For string/numeric defaults, use ne and != appropriately or just string compare since it's for display
- print " (default: $meta->{default})";
- }
- print "\n";
- }
- print "\n";
- }
- exit 0;
-}
-
-sub prettyprint {
- print $_[0] . "\n" unless ( $opt{'silent'} or $opt{'json'} );
- print $fh $_[0] . "\n" if defined($fh);
-}
-
-sub goodprint {
- prettyprint $good. " " . $_[0] unless ( $opt{nogood} == 1 );
-}
-
-sub infoprint {
- prettyprint $info. " " . $_[0] unless ( $opt{noinfo} == 1 );
-}
-
-sub badprint {
- prettyprint $bad. " " . $_[0] unless ( $opt{nobad} == 1 );
-}
-
-sub debugprint {
- prettyprint $deb. " " . $_[0] unless ( $opt{debug} == 0 );
-}
-
-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];
-}
-
-sub cmdprint {
- prettyprint $cmd. " " . $_[0] . $end;
-}
-
-sub push_recommendation {
- my ( $cat, $msg ) = @_;
- push @generalrec, $msg;
- push @sysrec, $msg if $cat =~ /sys/i;
- push @secrec, $msg if $cat =~ /sec/i;
- push @modeling, $msg if $cat =~ /mod/i;
-}
-
-sub infoprintml {
- for my $ln (@_) { $ln =~ s/\n//g; infoprint "\t$ln"; }
-}
-
-sub infoprintcmd {
- cmdprint "@_";
- infoprintml grep { $_ ne '' and $_ !~ /^\s*$/ } `@_ 2>&1`;
-}
-
-sub subheaderprint {
- my $tln = 100;
- my $sln = 8;
- my $ln = length("@_") + 2;
-
- prettyprint " ";
- prettyprint "-" x $sln . " @_ " . "-" x ( $tln - $ln - $sln );
-}
-
-sub infoprinthcmd {
- subheaderprint "$_[0]";
- infoprintcmd "$_[1]";
-}
-
-sub is_remote() {
- my $host = $opt{'host'};
- return 1 if ( $opt{'cloud'} && ( $opt{'ssh-host'} // '0' ) ne '0' );
- return 0 if ( ( $host // '0' ) eq '0' );
- return 0 if ( $host eq 'localhost' );
- return 0 if ( $host eq '127.0.0.1' );
- return 1;
-}
-
-sub is_docker() {
- return 1 if -f '/.dockerenv';
- if ( -f '/proc/self/cgroup' ) {
- if ( open( my $fh, '<', '/proc/self/cgroup' ) ) {
- while ( my $line = <$fh> ) {
- if ( $line =~ /docker|kubepods|containerd|podman/ ) {
- close $fh;
- return 1;
- }
- }
- close $fh;
- }
- }
- return 1
- if (
- (
- defined $ENV{'container'}
- && $ENV{'container'} =~ /^(docker|podman|lxc)$/
- )
- || ( defined $opt{'container'} && ( $opt{'container'} // '0' ) ne '0' )
- );
- return 0;
-}
-
-sub is_int {
- return 0 unless defined $_[0];
- my $str = $_[0];
-
- #trim whitespace both sides
- $str =~ s/^\s+|\s+$//g;
-
- #Alternatively, to match any float-like numeric, use:
- # m/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
-
- #flatten to string and match dash or plus and one or more digits
- if ( $str =~ /^(\-|\+)?\d+?$/ ) {
- return 1;
- }
- return 0;
-}
-
-# Calculates the number of physical cores
-sub cpu_cores {
- if ( $^O eq 'linux' ) {
- if ( get_transport_prefix() eq '' ) {
- my %cpus;
- my %cores;
- if ( open( my $proc_cpuinfo, '<', '/proc/cpuinfo' ) ) {
- while (<$proc_cpuinfo>) {
- if ( /^physical id\s*:\s*(.*)/ ) { $cpus{$1} = 1; }
- if ( /^core id\s*:\s*(.*)/ ) { $cores{$1} = 1; }
- }
- close $proc_cpuinfo;
- my $cntCPU = ( scalar keys %cpus ) * ( scalar keys %cores );
- return $cntCPU if $cntCPU > 0;
- }
- }
- my $cntCPU =
- execute_system_command(
-"awk -F: '/^core id/ && !P[\$2] { CORES++; P[\$2]=1 }; /^physical id/ && !N[\$2] { CPUs++; N[\$2]=1 }; END { print CPUs*CORES }' /proc/cpuinfo"
- );
- chomp $cntCPU;
- return ( $cntCPU == 0 ? execute_system_command("nproc") : $cntCPU ) + 0;
- }
-
- if ( $^O eq 'freebsd' ) {
- my $cntCPU = execute_system_command("sysctl -n kern.smp.cores");
- chomp $cntCPU;
- return $cntCPU + 0;
- }
- if ($is_win) {
- my $cntCPU =
- execute_system_command(
- 'wmic cpu get NumberOfCores| perl -ne "s/[^0-9]//g; print if /[0-9]+/;"'
- );
- chomp $cntCPU;
- return $cntCPU + 0;
- }
- return 0;
-}
-
-# Calculates the number of logical cores (including HT)
-sub logical_cpu_cores {
- if ( $^O eq 'linux' ) {
- if ( get_transport_prefix() eq '' ) {
- my $cntCPU = 0;
- if ( open( my $proc_cpuinfo, '<', '/proc/cpuinfo' ) ) {
- while (<$proc_cpuinfo>) {
- $cntCPU++ if /^processor\s*:\s*\d+/;
- }
- close $proc_cpuinfo;
- return $cntCPU if $cntCPU > 0;
- }
- }
- my $cntCPU = execute_system_command("grep -c ^processor /proc/cpuinfo");
- chomp $cntCPU;
- if ( $cntCPU == 0 ) {
- $cntCPU = execute_system_command("nproc");
- chomp $cntCPU;
- }
- return $cntCPU + 0;
- }
-
- if ( $^O eq 'freebsd' ) {
- my $cntCPU = execute_system_command("sysctl -n kern.smp.cpus");
- chomp $cntCPU;
- return $cntCPU + 0;
- }
- if ($is_win) {
- my $cntCPU =
- execute_system_command(
- 'wmic cpu get NumberOfLogicalProcessors| perl -ne "s/[^0-9]//g; print if /[0-9]+/;"'
- );
- chomp $cntCPU;
- return $cntCPU + 0;
- }
- return cpu_cores();
-}
-
-# Calculates the parameter passed in bytes, then rounds it to one decimal place
-sub hr_bytes {
- my $num = shift;
- return "0B" unless defined($num);
- return "0B" if $num eq "NULL";
- return "0B" if $num eq "";
-
- 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, then rounds it to a practical power-of-2 value in GB.
-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 $power_of_2_gb = 1;
- while ( $power_of_2_gb < $gbs ) {
- $power_of_2_gb *= 2;
- }
-
- return $power_of_2_gb . "G";
-}
-
-sub hr_raw {
- my $num = shift;
- return "0" unless defined($num);
- return "0" if $num eq "NULL";
- if ( $num =~ /^(\d+)G$/ ) {
- return $1 * 1024 * 1024 * 1024;
- }
- if ( $num =~ /^(\d+)M$/ ) {
- return $1 * 1024 * 1024;
- }
- if ( $num =~ /^(\d+)K$/ ) {
- return $1 * 1024;
- }
- if ( $num =~ /^(\d+)$/ ) {
- return $1;
- }
- return $num;
-}
-
-# Calculates the parameter passed in bytes, then rounds it to the nearest integer
-sub hr_bytes_rnd {
- my $num = shift;
- return "0B" unless defined($num);
- return "0B" if $num eq "NULL";
-
- 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;
- }
-}
-
-# Calculate Percentage
-sub percentage {
- my $value = shift;
- my $total = shift;
- $total = 0 unless defined $total;
- $total = 0 if $total eq "NULL";
- return "100.00" if $total == 0;
- return sprintf( "%.2f", ( $value * 100 / $total ) );
-}
-
-# Calculates uptime to display in a human-readable 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, $xargsflags );
-
-sub memerror {
- badprint
-"Unable to determine total memory/swap; use '--forcemem' and '--forceswap'";
- exit 1;
-}
-
-sub os_setup {
- my $prefix = get_transport_prefix();
- my $os;
- if ($is_win) {
- $os = 'windows';
- }
- elsif ( $prefix eq '' ) {
- $os = ( POSIX::uname() )[0];
- }
- else {
- $os = execute_system_command('uname');
- }
-
- $duflags = ( $os =~ /Linux/ ) ? '-b' : '';
- $xargsflags = ( $os =~ /Darwin|SunOS/ ) ? '' : '-r';
- if ( $opt{'forcemem'} > 0 ) {
- $physical_memory = $opt{'forcemem'} * 1048576;
- infoprint "Assuming $opt{'forcemem'} MB of physical memory";
- if ( $opt{'forceswap'} > 0 ) {
- $swap_memory = $opt{'forceswap'} * 1048576;
- infoprint "Assuming $opt{'forceswap'} MB of swap space";
- }
- else {
- $swap_memory = 0;
- badprint "Assuming 0 MB of swap space (use --forceswap to specify)";
- }
- }
- else {
- if ( $os =~ /Linux|CYGWIN/ ) {
- if ( $prefix eq '' && open( my $meminfo, '<', '/proc/meminfo' ) ) {
- while (<$meminfo>) {
- if (/^MemTotal:\s+(\d+)/i) { $physical_memory = $1 * 1024; }
- if (/^SwapTotal:\s+(\d+)/i) { $swap_memory = $1 * 1024; }
- }
- close $meminfo;
- }
-
- if ( !defined $physical_memory || $physical_memory == 0 ) {
- $physical_memory =
- execute_system_command(
- "grep -i memtotal: /proc/meminfo | awk '{print \$2}'")
- or memerror;
- $physical_memory *= 1024;
- }
-
- if ( !defined $swap_memory ) {
- $swap_memory =
- execute_system_command(
- "grep -i swaptotal: /proc/meminfo | awk '{print \$2}'")
- or memerror;
- $swap_memory *= 1024;
- }
- }
- elsif ( $os =~ /Darwin/ ) {
- $physical_memory = execute_system_command('sysctl -n hw.memsize')
- or memerror;
- $swap_memory =
- execute_system_command(
- "sysctl -n vm.swapusage | awk '{print \$3}' | sed 's/\..*\$//'")
- or memerror;
- }
- elsif ( $os =~ /NetBSD|OpenBSD|FreeBSD/ ) {
- $physical_memory = execute_system_command('sysctl -n hw.physmem')
- or memerror;
- if ( $physical_memory < 0 ) {
- $physical_memory =
- execute_system_command('sysctl -n hw.physmem64')
- or memerror;
- }
- $swap_memory =
- execute_system_command(
- "swapctl -l | grep '^/' | awk '{ s+= \$2 } END { print s }'")
- or memerror;
- }
- elsif ( $os =~ /BSD/ ) {
- $physical_memory = execute_system_command('sysctl -n hw.realmem')
- or memerror;
- $swap_memory =
- execute_system_command(
- "swapinfo | grep '^/' | awk '{ s+= \$2 } END { print s }'");
- }
- elsif ( $os =~ /SunOS/ ) {
- $physical_memory =
- execute_system_command(
- "/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 =
- execute_system_command(
- "lsattr -El sys0 | grep realmem | awk '{print \$2}'")
- or memerror;
- chomp($physical_memory);
- $physical_memory = $physical_memory * 1024;
- $swap_memory = execute_system_command(
- "lsps -as | awk -F'(MB| +)' '/MB /{print \$2}'")
- or memerror;
- chomp($swap_memory);
- $swap_memory = $swap_memory * 1024 * 1024;
- }
- elsif ( $os =~ /windows/i ) {
- $physical_memory =
- execute_system_command(
-'wmic ComputerSystem get TotalPhysicalMemory | perl -ne "s/[^0-9]//g; print if /[0-9]+/;'
- ) or memerror;
- $swap_memory =
- execute_system_command(
-'wmic OS get FreeVirtualMemory | perl -ne "s/[^0-9]//g; print if /[0-9]+/;'
- ) or memerror;
- }
- }
- debugprint "Physical Memory: $physical_memory";
- debugprint "Swap Memory: $swap_memory";
- chomp($physical_memory);
- chomp($swap_memory);
- chomp($os);
- $result{'OS'}{'OS Type'} = $os;
- $result{'OS'}{'Physical Memory'}{'bytes'} = $physical_memory;
- $result{'OS'}{'Physical Memory'}{'pretty'} = hr_bytes($physical_memory);
- $result{'OS'}{'Swap Memory'}{'bytes'} = $swap_memory;
- $result{'OS'}{'Swap Memory'}{'pretty'} = hr_bytes($swap_memory);
- $result{'OS'}{'Other Processes'}{'bytes'} = get_other_process_memory();
- $result{'OS'}{'Other Processes'}{'pretty'} =
- hr_bytes( get_other_process_memory() );
-}
-
-sub get_http_cli {
- my $httpcli = which( "curl", $ENV{'PATH'} );
- chomp($httpcli);
- if ($httpcli) {
- return $httpcli;
- }
-
- $httpcli = which( "wget", $ENV{'PATH'} );
- chomp($httpcli);
- if ($httpcli) {
- return $httpcli;
- }
- return "";
-}
-
-# Checks for updates to MySQLTuner
-sub validate_tuner_version {
- if ( $opt{'checkversion'} eq 0 ) {
- print "\n" unless ( $opt{'silent'} or $opt{'json'} );
- infoprint "Skipped version check for MySQLTuner script";
- return;
- }
-
- my $url =
-'https://raw.githubusercontent.com/jmrenouard/MySQLTuner-perl/master/mysqltuner.pl';
- my $content;
-
- # Try HTTP::Tiny if available (Core since 5.13.9)
- if ( eval { require HTTP::Tiny; 1 } ) {
- debugprint "Using HTTP::Tiny for version check";
- my $http = HTTP::Tiny->new( timeout => 3 );
- my $response = $http->get($url);
- if ( $response->{success} ) {
- $content = $response->{content};
- }
- else {
- debugprint
- "HTTP::Tiny failed: $response->{status} $response->{reason}";
- }
- }
-
- # Fallback to curl/wget if content is still empty or HTTP::Tiny not available
- if ( !$content ) {
- debugprint "Falling back to curl/wget for version check";
- my $httpcli = get_http_cli();
- if ($httpcli) {
- my $cmd_line =
- ( $httpcli =~ /curl$/ )
- ? "$httpcli -sL $url"
- : "$httpcli -q -O - $url";
- $content = execute_system_command($cmd_line);
- }
- }
-
- if ($content) {
-
-# Robust regex for version extraction (handles my/our/local, spacing, and quotes)
- if ( $content =~
- /^\s*(?:our|my|local)\s+\$tunerversion\s*=\s*["']([\d.]+)["']\s*;/m
- )
- {
- my $update = $1;
- infoprint "VERSION: $update";
- compare_tuner_version($update);
- }
- else {
- badprint
- "Cannot determine latest tuner version from fetched content";
- }
- }
- else {
- badprint "Failed to fetch tuner version information from $url";
- }
- return;
-}
-
-# Checks for updates to MySQLTuner
-sub update_tuner_version {
- if ( $opt{'updateversion'} eq 0 ) {
- badprint "Skipped version update for MySQLTuner script";
- print "\n" unless ( $opt{'silent'} or $opt{'json'} );
- return;
- }
-
- my $update;
- my $fullpath = "";
- my $url =
- "https://raw.githubusercontent.com/jmrenouard/MySQLTuner-perl/master/";
- my @scripts =
- ( "mysqltuner.pl", "basic_passwords.txt", "vulnerabilities.csv" );
- my $totalScripts = scalar(@scripts);
- my $receivedScripts = 0;
- my $httpcli = get_http_cli();
-
- foreach my $script (@scripts) {
-
- if ( $httpcli =~ /curl$/ ) {
- debugprint "$httpcli is available.";
-
- $fullpath = dirname(__FILE__) . "/" . $script;
- debugprint "FullPath: $fullpath";
- debugprint
-"$httpcli -s --connect-timeout 3 '$url$script' 2>$devnull > $fullpath";
- $update =
- execute_system_command(
-"$httpcli -s --connect-timeout 3 '$url$script' 2>$devnull > $fullpath"
- );
- chomp($update);
- debugprint "$script updated: $update";
-
- if ( -s $script eq 0 ) {
- badprint "Couldn't update $script";
- }
- else {
- ++$receivedScripts;
- debugprint "$script updated: $update";
- }
- }
- elsif ( $httpcli =~ /wget$/ ) {
-
- debugprint "$httpcli is available.";
-
- debugprint
-"$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'";
- $update =
- execute_system_command(
-"$httpcli -qe timestamping=off -t 1 -T 3 -O $script '$url$script'"
- );
- chomp($update);
-
- if ( -s $script eq 0 ) {
- badprint "Couldn't update $script";
- }
- else {
- ++$receivedScripts;
- debugprint "$script updated: $update";
- }
- }
- else {
- debugprint "curl and wget are not available.";
- infoprint "Unable to check for the latest MySQLTuner version";
- }
-
- }
-
- if ( $receivedScripts eq $totalScripts ) {
- goodprint "Successfully updated MySQLTuner script";
- }
- else {
- badprint "Couldn't update MySQLTuner script";
- }
- infoprint "Stopping program: MySQLTuner script must be updated first.";
- exit 0;
-}
-
-sub compare_tuner_version {
- my $remoteversion = shift;
- debugprint "Remote data: $remoteversion";
-
- #exit 0;
- if ( $remoteversion ne $tunerversion ) {
- badprint
- "There is a new version of MySQLTuner available ($remoteversion)";
- update_tuner_version();
- return;
- }
- goodprint "You have the latest version of MySQLTuner ($tunerversion)";
- return;
-}
-
-# Checks to see if a MySQL login is possible
-our ( $mysqllogin, $doremote, $remotestring, $mysqlcmd, $mysqladmincmd );
-
-sub cloud_setup {
- if ( $opt{'cloud'} || $opt{'azure'} ) {
- $opt{'cloud'} = 1; # Ensure cloud is enabled if azure is
- infoprint "Cloud mode activated.";
- if ( $opt{'azure'} ) {
- infoprint
- "Azure-specific checks enabled (currently generic cloud checks).";
- }
- if ( $opt{'ssh-host'} ) {
- infoprint "Cloud SSH mode.";
- my @os_info = execute_system_command('uname -a');
- infoprint "Remote OS Info:";
- infoprintml @os_info;
- my @mem_info =
- execute_system_command('grep MemTotal /proc/meminfo');
- if ( scalar @mem_info > 0 && $mem_info[0] =~ /(\d+)/ ) {
- my $remote_mem_bytes = $1 * 1024;
- $opt{'forcemem'} = $remote_mem_bytes / 1048576;
- infoprint "Remote memory detected: "
- . hr_bytes($remote_mem_bytes);
- }
- else {
- badprint
-"Could not determine remote memory. Using --forcemem if provided, or default.";
- if ( $opt{'forcemem'} == 0 ) {
- $opt{'forcemem'} = 1024; # Default to 1GB
- }
- }
- my @swap_info =
- execute_system_command('grep SwapTotal /proc/meminfo');
- if ( scalar @swap_info > 0 && $swap_info[0] =~ /(\d+)/ ) {
- my $remote_swap_bytes = $1 * 1024;
- $opt{'forceswap'} = $remote_swap_bytes / 1048576;
- infoprint "Remote swap detected: "
- . hr_bytes($remote_swap_bytes);
- }
- else {
- infoprint "Could not determine remote swap. Assuming 0.";
- if ( $opt{'forceswap'} == 0 ) {
- $opt{'forceswap'} = 0;
- }
- }
- }
- else {
- infoprint "Direct DB Connection mode.";
- $opt{'nosysstat'} = 1;
- if ( $opt{'forcemem'} == 0 ) {
- badprint
- "Direct cloud connection requires --forcemem. Assuming 1GB.";
- $opt{'forcemem'} = 1024;
- }
- }
- }
-}
-
-sub get_ssh_prefix {
- return "" if not( $opt{'cloud'} and $opt{'ssh-host'} );
-
- my $ssh_base_cmd = 'ssh';
- if ( $opt{'ssh-identity-file'} ) {
- $ssh_base_cmd .= " -i '" . $opt{'ssh-identity-file'} . "'";
- }
- $ssh_base_cmd .=
- " -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null'";
- my $ssh_target = '';
- if ( $opt{'ssh-user'} ) {
- $ssh_target = $opt{'ssh-user'} . '@';
- }
- $ssh_target .= $opt{'ssh-host'};
-
- my $prefix;
- if ( $opt{'ssh-password'} ) {
- my $sshpass_path = which( "sshpass", $ENV{'PATH'} );
- if ($sshpass_path) {
- $prefix =
- "sshpass -p '"
- . $opt{'ssh-password'} . "' "
- . $ssh_base_cmd . " "
- . $ssh_target;
- }
- else {
- badprint
-"sshpass is not installed. Password authentication for SSH will not work.";
- $prefix = $ssh_base_cmd . " " . $ssh_target;
- }
- }
- else {
- $prefix = $ssh_base_cmd . " " . $ssh_target;
- }
- return $prefix . " ";
-}
-
-sub get_container_prefix {
- return "" if ( $opt{'container'} // '0' ) eq '0' or $opt{'container'} eq '';
- my ( $engine, $name ) =
- $opt{'container'} =~ /^(docker|podman|kubectl):(.*)/
- ? ( $1, $2 )
- : ( "docker", $opt{'container'} );
- if ( $engine eq "docker" || $engine eq "podman" ) {
- return "$engine exec $name sh -c ";
- }
- elsif ( $engine eq "kubectl" ) {
- return "kubectl exec $name -- sh -c ";
- }
- return "";
-}
-
-sub get_transport_prefix {
- my $prefix = get_ssh_prefix();
- return $prefix if $prefix ne '';
- return get_container_prefix();
-}
-
-sub execute_system_command {
- my ($command) = @_;
- my $ssh_prefix = get_ssh_prefix();
- my $container_prefix = get_container_prefix();
-
- # Avoid double transport if the command is already prefixed
- my $full_cmd = $command;
- if ( $ssh_prefix ne '' && index( $command, $ssh_prefix ) != 0 ) {
- $full_cmd = "$ssh_prefix '$command'";
- }
- elsif ( $container_prefix ne ''
- && index( $command, $container_prefix ) != 0 )
- {
- $command =~ s/'/'\\''/g;
- $full_cmd = "$container_prefix '$command'";
- }
-
- debugprint "Executing system command: $full_cmd";
- my @output = `$full_cmd 2>&1`;
-
- if ( $? != 0 ) {
-
- # Be less verbose for commands that are expected to fail on some systems
- if ( $command !~
-/(?:^|\/)(dmesg|lspci|dmidecode|ipconfig|isainfo|bootinfo|ver|wmic|lsattr|prtconf|swapctl|swapinfo|svcprop|ps|ping|ifconfig|ip|hostname|who|free|top|uptime|netstat|sysctl|mysql|mariadb|curl|wget)/
- )
- {
- badprint "System command failed: $command";
- infoprintml @output;
- }
- }
-
- # Return based on calling context
- return wantarray ? @output : join( "", @output );
-}
-
-if ($is_win) {
- eval { require Win32; } or last;
- my $osname = Win32::GetOSName();
- infoprint "* Windows OS ($osname) is not fully tested.\n";
-
- #exit 1;
-}
-
-sub mysql_setup {
- $doremote = 0;
- $remotestring = '';
- my $transport_prefix = get_transport_prefix();
-
- if ( $opt{mysqladmin} ) {
- $mysqladmincmd = $opt{mysqladmin};
- }
- else {
- if ( $transport_prefix ne '' ) {
- my $check = execute_system_command("which mariadb-admin");
- $mysqladmincmd =
- ( $check =~ /mariadb-admin/ ) ? "mariadb-admin" : "mysqladmin";
- }
- else {
- $mysqladmincmd =
- ( which( "mariadb-admin", $ENV{'PATH'} )
- || which( "mysqladmin", $ENV{'PATH'} ) );
- }
- }
- chomp($mysqladmincmd);
- if ( !$mysqladmincmd
- || ( $transport_prefix eq '' && !-x $mysqladmincmd ) )
- {
- badprint
- "Couldn't find an executable mysqladmin/mariadb-admin command.";
- exit 1;
- }
-
- if ( $opt{mysqlcmd} ) {
- $mysqlcmd = $opt{mysqlcmd};
- }
- else {
- if ( $transport_prefix ne '' ) {
- my $check = execute_system_command("which mariadb");
- $mysqlcmd = ( $check =~ /mariadb/ ) ? "mariadb" : "mysql";
- }
- else {
- $mysqlcmd =
- ( which( "mariadb", $ENV{'PATH'} )
- || which( "mysql", $ENV{'PATH'} ) );
- }
- }
- chomp($mysqlcmd);
- if ( !$mysqlcmd || ( $transport_prefix eq '' && !-x $mysqlcmd ) ) {
- badprint "Couldn't find an executable mysql/mariadb command.";
- exit 1;
- }
-
- # MySQL Client defaults
- $mysqlcmd =~ s/\n$//g;
- my $mysqlclidefaults = execute_system_command("$mysqlcmd --print-defaults");
- debugprint "MySQL Client: $mysqlclidefaults";
- if ( $mysqlclidefaults =~ /auto-vertical-output/ ) {
- badprint
- "Avoid auto-vertical-output in configuration file(s) for MySQL like";
- exit 1;
- }
-
- debugprint "MySQL Client: $mysqlcmd";
-
- # Are we being asked to connect via a socket?
- if ( $opt{socket} ne 0 ) {
- if ( $opt{port} ne 0 ) {
- $remotestring = " -S $opt{socket} -P $opt{port}";
- }
- else {
- $remotestring = " -S $opt{socket}";
- }
- }
-
- # Are we being asked to connect via a named pipe?
- if ( $opt{pipe} ne 0 ) {
- if ( $opt{pipe_name} ne 0 ) {
- $remotestring = " -W -S $opt{pipe_name}";
- }
- else {
- $remotestring = " -W";
- }
- }
-
- if ( $opt{protocol} ) {
- $remotestring = " --protocol=$opt{protocol}";
- }
-
- # 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 && is_remote eq 1 ) {
- badprint "The --forcemem option is required for remote connections";
- badprint
- "Assuming RAM memory is 1Gb for simplify remote connection usage";
- $opt{'forcemem'} = 1024;
-
- #exit 1;
- }
- if ( $opt{'forceswap'} eq 0 && is_remote eq 1 ) {
- badprint
- "The --forceswap option is required for remote connections";
- badprint
- "Assuming Swap size is 1Gb for simplify remote connection usage";
- $opt{'forceswap'} = 1024;
-
- #exit 1;
- }
- infoprint "Performing tests on $opt{host}:$opt{port}";
- $remotestring = " -h $opt{host} -P $opt{port}";
- $doremote = is_remote();
-
- }
- else {
- $opt{host} = '127.0.0.1';
- }
-
- if ( $opt{'ssl-ca'} ne 0 ) {
- if ( -e -r -f $opt{'ssl-ca'} ) {
- $remotestring .= " --ssl-ca=$opt{'ssl-ca'}";
- infoprint
- "Will connect using ssl public key passed on the command line";
- }
- else {
- badprint
-"Attempted to use passed ssl public key, but it was not found or could not be read";
- exit 1;
- }
- }
-
- if ( $transport_prefix ne '' && ( $opt{pass} // '0' ) eq '0' ) {
- if ( ( $ENV{MARIADB_ROOT_PASSWORD} // '' ) ne ''
- || ( $ENV{MYSQL_ROOT_PASSWORD} // '' ) ne '' )
- {
- $opt{pass} = $ENV{MARIADB_ROOT_PASSWORD} || $ENV{MYSQL_ROOT_PASSWORD};
- debugprint "Detected password from container environment";
- }
- }
-
- # Did we already get a username with or without password on the command line?
- if ( $opt{user} ne 0 || $opt{container} ) {
- my $username = $opt{user} ne 0 ? $opt{user} : "root";
- $mysqllogin =
- "-u $username "
- . ( ( $opt{pass} ne 0 ) ? "-p'$opt{pass}' " : " " )
- . $remotestring;
- my $loginstatus =
- execute_system_command(
- "$mysqlcmd -Nrs -e 'select \"mysqld is alive\";' $mysqllogin");
- if ( $loginstatus =~ /mysqld is alive/ ) {
- goodprint "Logged in using credentials passed on the command line";
- return 1;
- }
- }
-
- my $svcprop = which( "svcprop", $ENV{'PATH'} );
- if ( substr( $svcprop, 0, 1 ) =~ "/" ) {
-
- # We are on solaris
- (
- my $mysql_login = execute_system_command(
-"svcprop -p quickbackup/username svc:/network/mysql-quickbackup:default"
- )
- ) =~ s/\s+$//;
- (
- my $mysql_pass = execute_system_command(
-"svcprop -p quickbackup/password svc:/network/mysql-quickbackup:default"
- )
- ) =~ s/\s+$//;
- if ( substr( $mysql_login, 0, 7 ) ne "svcprop" ) {
-
- # mysql-quickbackup is installed
- $mysqllogin = "-u $mysql_login -p$mysql_pass";
- my $loginstatus =
- execute_system_command("mysqladmin $mysqllogin ping");
- if ( $loginstatus =~ /mysqld is alive/ ) {
- goodprint "Logged in using credentials from mysql-quickbackup.";
- return 1;
- }
- else {
- badprint
-"Attempted to use login credentials from mysql-quickbackup, but they failed.";
- exit 1;
- }
- }
- }
- elsif ( -r "/etc/psa/.psa.shadow" and $doremote == 0 ) {
-
- # It's a Plesk box, use the available credentials
- $mysqllogin =
- "-u admin -p" . execute_system_command("cat /etc/psa/.psa.shadow");
- my $loginstatus =
- execute_system_command("$mysqladmincmd ping $mysqllogin");
- unless ( $loginstatus =~ /mysqld is alive/ ) {
-
- # Plesk 10+
- $mysqllogin =
- "-u admin -p"
- . execute_system_command(
- "/usr/local/psa/bin/admin --show-password");
- $loginstatus =
- execute_system_command("$mysqladmincmd ping $mysqllogin");
- unless ( $loginstatus =~ /mysqld is alive/ ) {
- badprint
-"Attempted to use login credentials from Plesk and Plesk 10+, but they failed.";
- exit 1;
- }
- }
- }
- elsif ( -r "/usr/local/directadmin/conf/mysql.conf" and $doremote == 0 ) {
-
- # It's a DirectAdmin box, use the available credentials
- my $mysqluser =
- execute_system_command(
- "cat /usr/local/directadmin/conf/mysql.conf | egrep '^user=.*'");
- my $mysqlpass =
- execute_system_command(
- "cat /usr/local/directadmin/conf/mysql.conf | egrep '^passwd=.*'");
-
- $mysqluser =~ s/user=//;
- $mysqluser =~ s/[\r\n]//;
- $mysqlpass =~ s/passwd=//;
- $mysqlpass =~ s/[\r\n]//;
-
- $mysqllogin = "-u $mysqluser -p$mysqlpass";
-
- my $loginstatus = execute_system_command("mysqladmin ping $mysqllogin");
- unless ( $loginstatus =~ /mysqld is alive/ ) {
- badprint
-"Attempted to use login credentials from DirectAdmin, but they failed.";
- exit 1;
- }
- }
- elsif ( -r "/etc/mysql/debian.cnf"
- and $doremote == 0
- and $opt{'defaults-file'} eq '' )
- {
-
- # We have a Debian maintenance account, use it
- $mysqllogin = "--defaults-file=/etc/mysql/debian.cnf";
- my $loginstatus =
- execute_system_command("$mysqladmincmd $mysqllogin ping");
- if ( $loginstatus =~ /mysqld is alive/ ) {
- goodprint
- "Logged in using credentials from Debian maintenance account.";
- return 1;
- }
- else {
- badprint
-"Attempted to use login credentials from Debian maintenance account, but they failed.";
- exit 1;
- }
- }
- elsif ( $opt{'defaults-file'} and -r "$opt{'defaults-file'}" ) {
-
- # defaults-file
- debugprint "defaults file detected: $opt{'defaults-file'}";
- my $mysqlclidefaults =
- execute_system_command("$mysqlcmd --print-defaults");
- debugprint "MySQL Client Default File: $opt{'defaults-file'}";
-
- $mysqllogin = "--defaults-file=" . $opt{'defaults-file'};
- my $loginstatus =
- execute_system_command("$mysqladmincmd $mysqllogin ping");
- if ( $loginstatus =~ /mysqld is alive/ ) {
- goodprint "Logged in using credentials from defaults file account.";
- return 1;
- }
- }
- elsif ( $opt{'defaults-extra-file'}
- and -r "$opt{'defaults-extra-file'}" )
- {
-
- # defaults-extra-file
- debugprint "defaults extra file detected: $opt{'defaults-extra-file'}";
- my $mysqlclidefaults =
- execute_system_command("$mysqlcmd --print-defaults");
- debugprint
- "MySQL Client Extra Default File: $opt{'defaults-extra-file'}";
-
- $mysqllogin = "--defaults-extra-file=" . $opt{'defaults-extra-file'};
- my $loginstatus =
- execute_system_command("$mysqladmincmd $mysqllogin ping");
- if ( $loginstatus =~ /mysqld is alive/ ) {
- goodprint
- "Logged in using credentials from extra defaults file account.";
- return 1;
- }
- }
- else {
- # It's not Plesk or Debian, we should try a login
- debugprint "$mysqladmincmd $remotestring ping 2>&1";
-
- #my $loginstatus = "";
- debugprint "Using mysqlcmd: $mysqlcmd";
-
- #if (defined($mysqladmincmd)) {
- # infoprint "Using mysqladmin to check login";
- # $loginstatus=`$mysqladmincmd $remotestring ping 2>&1`;
- #} else {
- infoprint "Using mysql to check login";
- my $loginstatus =
- execute_system_command(
-"$mysqlcmd $remotestring -Nrs -e 'select \"mysqld is alive\"' --connect-timeout=3"
- );
-
- #}
-
- 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 =
- $is_win
- ? ( $ENV{MARIADB_HOME} || $ENV{MYSQL_HOME} || $ENV{USERPROFILE} )
- : ( $ENV{HOME} // '' );
- if ( length($userpath) > 0 ) {
- chomp($userpath);
- }
- unless ( -e "${userpath}/.my.cnf" or -e "${userpath}/.mylogin.cnf" )
- {
- badprint
- "SECURITY RISK: Successfully authenticated without password";
- }
- return 1;
- }
- else {
- if ( $opt{'noask'} == 1 ) {
- badprint
- "Attempted to use login credentials, but they were invalid";
- exit 1;
- }
- my ( $name, $password );
-
- # If --user is defined no need to ask for username
- if ( $opt{user} ne 0 ) {
- $name = $opt{user};
- }
- else {
- print STDERR "Please enter your MySQL administrative login: ";
- $name = ;
- }
-
- # If --pass is defined no need to ask for password
- if ( $opt{pass} ne 0 ) {
- $password = $opt{pass};
- }
- else {
- print STDERR
- "Please enter your MySQL administrative password: ";
- system("stty -echo >$devnull 2>&1");
- $password = ;
- system("stty echo >$devnull 2>&1");
- }
- chomp($password);
- chomp($name);
- $mysqllogin = "-u $name";
-
- if ( length($password) > 0 ) {
- if ($is_win) {
- $mysqllogin .= " -p\"$password\"";
- }
- else {
- $mysqllogin .= " -p'$password'";
- }
- }
- $mysqllogin .= $remotestring;
- my $loginstatus =
- execute_system_command("$mysqladmincmd ping $mysqllogin");
- if ( $loginstatus =~ /mysqld is alive/ ) {
-
- #print STDERR "";
- if ( !length($password) ) {
-
- # Did this go well because of a .my.cnf file or is there no password set?
- my $userpath =
- $is_win
- ? ( $ENV{MARIADB_HOME}
- || $ENV{MYSQL_HOME}
- || $ENV{USERPROFILE} )
- : ( $ENV{HOME} // '' );
- chomp($userpath);
- unless ( -e "$userpath/.my.cnf" ) {
- print STDERR "";
- badprint
-"SECURITY RISK: Successfully authenticated without password";
- }
- }
- return 1;
- }
- else {
- #print STDERR "";
- badprint
- "Attempted to use login credentials, but they were invalid.";
- exit 1;
- }
- exit 1;
- }
- }
-}
-
-sub build_mysql_connection_command {
- return "$mysqlcmd $mysqllogin";
-}
-
-# MySQL Request Array
-sub select_array {
- my $req = shift;
- debugprint "PERFORM: $req ";
- my $req_escaped = $req;
- $req_escaped =~ s/"/\\"/g;
- my @result =
- execute_system_command(
- "$mysqlcmd $mysqllogin -Bse \"$req_escaped\" 2>>$devnull");
- if ( $? != 0 ) {
- badprint "Failed to execute: $req";
- badprint "FAIL Execute SQL / return code: $?";
- if ( $opt{debug} ) {
- debugprint execute_system_command(
- "$mysqlcmd $mysqllogin -Bse \"$req_escaped\" 2>&1");
- }
-
- #exit $?;
- return ();
- }
- debugprint "select_array: return code : $?";
- chomp(@result);
- return @result;
-}
-
-# MySQL Request Array
-sub select_array_with_headers {
- my $req = shift;
- debugprint "PERFORM: $req ";
- my $req_escaped = $req;
- $req_escaped =~ s/"/\\"/g;
- my @result =
- execute_system_command(
- "$mysqlcmd $mysqllogin -Bre \"$req_escaped\" 2>>$devnull");
- if ( $? != 0 ) {
- badprint "Failed to execute: $req";
- badprint "FAIL Execute SQL / return code: $?";
- if ( $opt{debug} ) {
- debugprint execute_system_command(
- "$mysqlcmd $mysqllogin -Bse \"$req_escaped\" 2>&1");
- }
-
- #exit $?;
- }
- debugprint "select_array_with_headers: return code : $?";
- chomp(@result);
- return @result;
-}
-
-# MySQL Request Array
-sub select_csv_file {
- my $tfile = shift;
- my $req = shift;
- debugprint "PERFORM: $req CSV into $tfile";
-
- #return;
- my @result = select_array_with_headers($req);
- open( my $fh, '>', $tfile ) or die "Could not open file '$tfile' $!";
- for my $l (@result) {
- $l =~ s/\t/","/g;
- $l =~ s/^/"/;
- $l =~ s/$/"\n/;
- print $fh $l;
- print $l if $opt{debug};
- }
- close $fh;
- infoprint "CSV file $tfile created";
-}
-
-sub human_size {
- my ( $size, $n ) = ( shift, 0 );
- ++$n and $size /= 1024 until $size < 1024;
- return sprintf "%.2f %s", $size, (qw[ bytes KB MB GB TB ])[$n];
-}
-
-# MySQL Request one
-sub select_one {
- my $req = shift;
- debugprint "PERFORM: $req ";
- my $result =
- execute_system_command("$mysqlcmd $mysqllogin -Bse \"$req\" 2>>$devnull");
- if ( $? != 0 ) {
- badprint "Failed to execute: $req";
- badprint "FAIL Execute SQL / return code: $?";
- if ( $opt{debug} ) {
- debugprint execute_system_command(
- "$mysqlcmd $mysqllogin -Bse \"$req\" 2>&1");
- }
-
- #exit $?;
- return "";
- }
- debugprint "select_array: return code : $?";
- chomp($result);
- return $result;
-}
-
-# MySQL Request one
-sub select_one_g {
- my $pattern = shift;
-
- my $req = shift;
- debugprint "PERFORM: $req ";
- my @result = execute_system_command(
- "$mysqlcmd $mysqllogin -re \"$req\\G\" 2>>$devnull");
- if ( $? != 0 ) {
- badprint "Failed to execute: $req";
- badprint "FAIL Execute SQL / return code: $?";
- if ( $opt{debug} ) {
- debugprint execute_system_command(
- "$mysqlcmd $mysqllogin -Bse \"$req\" 2>&1");
- }
-
- #exit $?;
- }
- debugprint "select_array: return code : $?";
- chomp(@result);
- return ( grep { /$pattern/ } @result )[0];
-}
-
-sub select_str_g {
- my $pattern = shift;
-
- my $req = shift;
- my $str = select_one_g $pattern, $req;
- return () unless defined $str;
- my @val = split /:/, $str;
- shift @val;
- return trim(@val);
-}
-
-sub select_user_dbs {
- return select_array(
-"SELECT DISTINCT TABLE_SCHEMA FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'percona', 'sys')"
- );
-}
-
-sub select_tables_db {
- my $schema = shift;
- return select_array(
-"SELECT DISTINCT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$schema'"
- );
-}
-
-sub select_indexes_db {
- my $schema = shift;
- return select_array(
-"SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema'"
- );
-}
-
-sub select_views_db {
- my $schema = shift;
- return select_array(
-"SELECT DISTINCT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA='$schema'"
- );
-}
-
-sub select_triggers_db {
- my $schema = shift;
- return select_array(
-"SELECT DISTINCT TRIGGER_NAME FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='$schema'"
- );
-}
-
-sub select_routines_db {
- my $schema = shift;
- return select_array(
-"SELECT DISTINCT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA='$schema'"
- );
-}
-
-sub select_table_indexes_db {
- my $schema = shift;
- my $tbname = shift;
- return select_array(
-"SELECT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$tbname'"
- );
-}
-
-sub select_table_columns_db {
- my $schema = shift;
- my $table = shift;
- return select_array(
-"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$schema' AND TABLE_NAME='$table'"
- );
-}
-
-sub get_password_column_name {
- my @mysql_user_columns = select_table_columns_db( 'mysql', 'user' );
- my $pass_column = '';
- my $auth_column = '';
-
- if ( grep { /^authentication_string$/msx } @mysql_user_columns ) {
- $auth_column = 'authentication_string';
- }
-
- # Case-insensitive match for Password/password
- my @pass_matches = grep { lc($_) eq 'password' } @mysql_user_columns;
- if (@pass_matches) {
- $pass_column = $pass_matches[0];
- }
-
- if ( $auth_column && $pass_column ) {
- return "IF(plugin='mysql_native_password', $auth_column, $pass_column)";
- }
- elsif ($auth_column) {
- return $auth_column;
- }
- elsif ($pass_column) {
- return $pass_column;
- }
-
- return '';
-}
-
-sub get_tuning_info {
- my @infoconn = select_array "\\s";
- my ( $tkey, $tval );
- @infoconn =
- grep { !/Threads:/ and !/Connection id:/ and !/pager:/ and !/Using/ }
- @infoconn;
- foreach my $line (@infoconn) {
- if ( $line =~ /\s*(.*):\s*(.*)/ ) {
- debugprint "$1 => $2";
- $tkey = $1;
- $tval = $2;
- chomp($tkey);
- chomp($tval);
- $result{'MySQL Client'}{$tkey} = $tval;
- }
- }
- $result{'MySQL Client'}{'Client Path'} = $mysqlcmd;
- $result{'MySQL Client'}{'Admin Path'} = $mysqladmincmd;
- $result{'MySQL Client'}{'Authentication Info'} = $mysqllogin;
-
-}
-
-# Populates all of the variable and status hashes
-our ( %mystat, %myvar, $dummyselect, %myrepl, %myslaves, %mycalc );
-
-sub arr2hash {
- my $href = shift;
- my $harr = shift;
- my $sep = shift;
- my $key = '';
- my $val = '';
-
- $sep = '\s' unless defined($sep);
- foreach my $line (@$harr) {
- next if ( $line =~ m/^\*\*\*\*\*\*\*/ );
- $line =~ /([a-zA-Z0-9_\/]*)\s*$sep\s*(.*)/;
- $key = $1;
- $val = $2;
- $$href{$key} = $val;
-
- debugprint " * $key = $val" if $key =~ /$opt{dbgpattern}/i;
- }
-}
-
-sub check_privileges {
- debugprint "Checking database privileges...";
- my @grants = select_array("SHOW GRANTS FOR CURRENT_USER()");
- my $all_grants = join( " ", @grants );
-
- # If the user has ALL PRIVILEGES or SUPER, we assume they have enough
- if ( $all_grants =~ /ALL PRIVILEGES/i || $all_grants =~ /SUPER/i ) {
- debugprint "Current user has high-level privileges (ALL or SUPER).";
- return;
- }
-
- my @required_privs =
- ( 'SELECT', 'PROCESS', 'EXECUTE', 'SHOW DATABASES', 'SHOW VIEW' );
-
- # Version-specific privileges
- if ( mysql_version_ge( 8, 0 ) && $myvar{'version'} !~ /mariadb/i ) {
- push( @required_privs, 'REPLICATION SLAVE', 'REPLICATION CLIENT' );
- }
- elsif ( $myvar{'version'} =~ /mariadb/i && mysql_version_ge( 10, 5 ) ) {
- push( @required_privs,
- 'BINLOG MONITOR',
- 'REPLICATION MASTER ADMIN',
- 'SLAVE MONITOR' );
-
-# MariaDB 11+ might use REPLICA MONITOR instead of SLAVE MONITOR, but SLAVE MONITOR is usually still there as an alias
- }
- else {
- push( @required_privs, 'REPLICATION CLIENT' );
- }
-
- my @missing_privs = ();
- foreach my $priv (@required_privs) {
-
- # Use word boundaries and case-insensitive matching
- if ( $all_grants !~ /\b$priv\b/i ) {
- push( @missing_privs, $priv );
- }
- }
-
- if (@missing_privs) {
- badprint "Current user is missing the following privileges: "
- . join( ", ", @missing_privs );
- badprint "Some checks may be skipped or provide incomplete results.";
- infoprint "Refer to README.md for the minimum required privileges.";
- }
- else {
- debugprint "Current user has all required privileges.";
- }
-}
-
-sub get_all_vars {
-
- # We need to initiate at least one query so that our data is useable
- $dummyselect = select_one "SELECT VERSION()";
- if ( not defined($dummyselect) or $dummyselect eq "" ) {
- badprint
- "You probably do not have enough privileges to run MySQLTuner ...";
- exit(256);
- }
- $dummyselect =~ s/(.*?)\-.*/$1/;
- debugprint "VERSION: " . $dummyselect . "";
- $result{'MySQL Client'}{'Version'} = $dummyselect;
-
- my @mysqlvarlist = select_array("SHOW VARIABLES");
- push( @mysqlvarlist, select_array("SHOW GLOBAL VARIABLES") );
- arr2hash( \%myvar, \@mysqlvarlist );
- $result{'Variables'} = \%myvar;
-
- # Check privileges after we have version and variable information
- check_privileges();
-
- my @mysqlstatlist = select_array("SHOW STATUS");
- push( @mysqlstatlist, select_array("SHOW GLOBAL STATUS") );
- arr2hash( \%mystat, \@mysqlstatlist );
- $result{'Status'} = \%mystat;
- unless ( defined( $myvar{'innodb_support_xa'} ) ) {
- $myvar{'innodb_support_xa'} = 'ON';
- }
- $mystat{'Uptime'} = 1
- unless defined( $mystat{'Uptime'} )
- and $mystat{'Uptime'} > 0;
- $myvar{'have_galera'} = "NO";
- if ( defined( $myvar{'wsrep_provider_options'} )
- && $myvar{'wsrep_provider_options'} ne ""
- && $myvar{'wsrep_on'} ne "OFF" )
- {
- $myvar{'have_galera'} = "YES";
- debugprint "Galera options: " . $myvar{'wsrep_provider_options'};
- }
-
- # Workaround for MySQL bug #59393 wrt. ignore-builtin-innodb
- if ( ( $myvar{'ignore_builtin_innodb'} || "" ) eq "ON" ) {
- $myvar{'have_innodb'} = "NO";
- }
-
- # Support GTID MODE FOR MARIADB
- # Issue MariaDB GTID mode #513
- $myvar{'gtid_mode'} = 'ON'
- if ( defined( $myvar{'gtid_current_pos'} )
- and $myvar{'gtid_current_pos'} ne '' );
-
- # Whether the server uses a thread pool to handle client connections
- # MariaDB: thread_handling = pool-of-threads
- # MySQL: thread_handling = loaded-dynamically
- $myvar{'have_threadpool'} = "NO";
- if (
- defined( $myvar{'thread_handling'} )
- and ( $myvar{'thread_handling'} eq 'pool-of-threads'
- || $myvar{'thread_handling'} eq 'loaded-dynamically' )
- )
- {
- $myvar{'have_threadpool'} = "YES";
- }
-
- # 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 = select_array "SHOW ENGINES";
- 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;
- $result{'Storage Engines'}{$engine} = $2;
- }
- }
-
- #debugprint Dumper(@mysqlenginelist);
-
- my @mysqlslave;
-
- # Issue #553: Fix replication command compatibility
- # MySQL 8.0+: SHOW REPLICA STATUS (deprecated: SHOW SLAVE STATUS)
- # MariaDB 10.5+: SHOW REPLICA STATUS (deprecated: SHOW SLAVE STATUS)
- # Older versions: SHOW SLAVE STATUS
- my $is_mysql8 =
- ( $myvar{'version'} =~ /^8\./ && $myvar{'version'} !~ /mariadb/i );
- my $is_mariadb105 =
- ( $myvar{'version'} =~ /mariadb/i && mysql_version_ge( 10, 5 ) );
-
- if ( $is_mysql8 or $is_mariadb105 ) {
- @mysqlslave = select_array("SHOW REPLICA STATUS\\G");
- }
- else {
- @mysqlslave = select_array("SHOW SLAVE STATUS\\G");
- }
- arr2hash( \%myrepl, \@mysqlslave, ':' );
- $result{'Replication'}{'Status'} = \%myrepl;
-
- # Issue #553: Fix slave/replica host listing commands
- # MySQL 8.0+: SHOW REPLICAS (deprecated: SHOW SLAVE HOSTS)
- # MariaDB 10.5+: SHOW REPLICA HOSTS (deprecated: SHOW SLAVE HOSTS)
- # Older versions: SHOW SLAVE HOSTS
- my @mysqlslaves;
- if ($is_mysql8) {
- @mysqlslaves = select_array("SHOW REPLICAS");
- }
- elsif ($is_mariadb105) {
- @mysqlslaves = select_array("SHOW REPLICA HOSTS\\G");
- }
- else {
- @mysqlslaves = select_array("SHOW SLAVE HOSTS\\G");
- }
-
- my @lineitems = ();
- foreach my $line (@mysqlslaves) {
- debugprint "L: $line ";
- @lineitems = split /\s+/, $line;
- $myslaves{ $lineitems[0] } = $line;
- $result{'Replication'}{'Slaves'}{ $lineitems[0] } = $lineitems[4];
- }
-
- # InnoDB Transaction Info
- if ( $myvar{'have_innodb'} eq "YES" ) {
- if ( mysql_version_ge(5) ) {
- $mycalc{'innodb_active_transactions'} =
- select_one("SELECT COUNT(*) FROM information_schema.INNODB_TRX");
- $mycalc{'innodb_longest_transaction_duration'} = select_one(
-"SELECT IFNULL(MAX(TIMESTAMPDIFF(SECOND, trx_started, NOW())),0) FROM information_schema.INNODB_TRX"
- );
- }
- }
-}
-
-sub remove_cr {
- return map {
- my $line = $_;
- $line =~ s/\n$//g;
- $line =~ s/^\s+$//g;
- $line;
- } @_;
-}
-
-sub remove_empty {
- grep { $_ ne '' } @_;
-}
-
-sub grep_file_contents {
- my $file = shift;
- my $patt;
-}
-
-sub get_file_contents {
- my $file = shift;
- open( my $fh, "<", $file ) or die "Can't open $file for read: $!";
- my @lines = <$fh>;
- close $fh or die "Cannot close $file: $!";
- @lines = remove_cr(@lines);
- return @lines;
-}
-
-sub get_basic_passwords {
- return get_file_contents(shift);
-}
-
-sub get_log_file_real_path {
- my $file = shift;
- my $hostname = shift;
- my $datadir = shift;
- if ( -f "$file" ) {
- return $file;
- }
- elsif ( -f "$hostname.log" ) {
- return "$hostname.log";
- }
- elsif ( -f "$hostname.err" ) {
- return "$hostname.err";
- }
- elsif ( -f "$datadir$hostname.err" ) {
- return "$datadir$hostname.err";
- }
- elsif ( -f "$datadir$hostname.log" ) {
- return "$datadir$hostname.log";
- }
- elsif ( -f "$datadir" . "mysql_error.log" ) {
- return "$datadir" . "mysql_error.log";
- }
- elsif ( -f "/var/log/mysql.log" ) {
- return "/var/log/mysql.log";
- }
- elsif ( -f "/var/log/mysqld.log" ) {
- return "/var/log/mysqld.log";
- }
- elsif ( -f "/var/log/mysql/$hostname.err" ) {
- return "/var/log/mysql/$hostname.err";
- }
- elsif ( -f "/var/log/mysql/$hostname.log" ) {
- return "/var/log/mysql/$hostname.log";
- }
- elsif ( -f "/var/log/mysql/" . "mysql_error.log" ) {
- return "/var/log/mysql/" . "mysql_error.log";
- }
- else {
- return $file;
- }
-}
-
-sub log_file_recommendations {
- my $has_pfs_error_log = 0;
- if ( $opt{'dbstat'} ) {
- my $pfs_result = select_one(
-"SELECT 1 FROM information_schema.tables WHERE table_schema='performance_schema' AND table_name='error_log' LIMIT 1"
- );
- $has_pfs_error_log = 1 if $pfs_result;
- }
-
- if ( is_remote eq 1 && !$has_pfs_error_log ) {
- infoprint
-"Skipping error log files checks on remote host (No Performance Schema error_log)";
- return;
- }
- my $fh;
- $myvar{'log_error'} = $opt{'server-log'}
- || get_log_file_real_path( $myvar{'log_error'}, $myvar{'hostname'},
- $myvar{'datadir'} );
-
- # Use explicit container if provided
- if ( $opt{'container'} ) {
- my $container_cmd = "docker";
- if ( $opt{'container'} =~ /^(docker|podman|kubectl):(.*)/ ) {
- $myvar{'log_error'} = $opt{'container'};
- }
- else {
- if ( which( "podman", $ENV{'PATH'} )
- && !which( "docker", $ENV{'PATH'} ) )
- {
- $container_cmd = "podman";
- }
- $myvar{'log_error'} = "$container_cmd:$opt{'container'}";
- }
- debugprint "Using explicit container: $myvar{'log_error'}";
- }
-
- # Try to find logs from docker/podman if file doesn't exist locally
- elsif (!-f "$myvar{'log_error'}"
- && $myvar{'log_error'} !~ /^(docker|podman|kubectl|systemd):/
- && !is_docker() )
- {
- my $container_cmd = "";
- if ( which( "docker", $ENV{'PATH'} ) ) {
- $container_cmd = "docker";
- }
- elsif ( which( "podman", $ENV{'PATH'} ) ) {
- $container_cmd = "podman";
- }
-
- if ( $container_cmd ne "" ) {
- my $port = $opt{'port'} || 3306;
- my $container =
- execute_system_command(
-"$container_cmd ps --filter \"publish=$port\" --format \"{{.Names}}\" | grep -vEi \"traefik|haproxy|maxscale|maxsale|proxy\" | head -n 1"
- );
- chomp $container;
- if ( $container eq "" ) {
- $container =
- execute_system_command(
-"$container_cmd ps --format \"{{.Names}} {{.Image}}\" | grep -Ei \"mysql|mariadb|percona|db|database\" | grep -vEi \"traefik|haproxy|maxscale|maxsale|proxy\" | head -n 1 | awk '{print \$1}'"
- );
- chomp $container;
- }
- if ( $container ne "" ) {
- $myvar{'log_error'} = "$container_cmd:$container";
- debugprint "Detected $container_cmd container: $container";
- }
- }
- }
-
- subheaderprint "Log file Recommendations";
- if ( $has_pfs_error_log && !$opt{'server-log'} ) {
- goodprint "Performance Schema error_log table detected";
- my $pfs_count =
- select_one("SELECT COUNT(*) FROM performance_schema.error_log");
- infoprint "Performance Schema error_log: $pfs_count entries detected";
-
- # Build mysql command for streaming output
- my $mysql_conn = build_mysql_connection_command();
- open( $fh, '-|',
-"$mysql_conn -N -s -e \"SELECT DATA FROM performance_schema.error_log ORDER BY LOGGED DESC LIMIT $maxlines\""
- ) || debugprint "Failed to open PFS error_log stream";
- $myvar{'log_error'} = "performance_schema.error_log";
- }
- elsif ( "$myvar{'log_error'}" eq "stderr" ) {
- badprint
-"log_error is set to $myvar{'log_error'}, but this script can't read stderr";
- return;
- }
- elsif ( $myvar{'log_error'} =~ /^(docker|podman|kubectl):(.*)/ ) {
- open( $fh, '-|', "$1 logs --tail=$maxlines '$2'" )
- // die "Can't start $1 $!";
- goodprint "Log from cloud` $myvar{'log_error'} exists";
- }
- elsif ( $myvar{'log_error'} =~ /^systemd:(.*)/ ) {
- open( $fh, '-|', "journalctl -n $maxlines -b -u '$1'" )
- // die "Can't start journalctl $!";
- goodprint "Log journal` $myvar{'log_error'} exists";
- }
- elsif ( -f "$myvar{'log_error'}" ) {
- goodprint "Log file $myvar{'log_error'} exists";
- my $size = ( stat $myvar{'log_error'} )[7];
- infoprint "Log file: "
- . $myvar{'log_error'} . " ("
- . hr_bytes_rnd($size) . ")";
-
- if ( $size > 0 ) {
- goodprint "Log file $myvar{'log_error'} is not empty";
- if ( $size < 32 * 1024 * 1024 ) {
- goodprint "Log file $myvar{'log_error'} is smaller than 32 MB";
- }
- else {
- badprint "Log file $myvar{'log_error'} is bigger than 32 MB";
- push @generalrec,
- $myvar{'log_error'}
- . " is > 32MB, you should analyze why or implement a rotation log strategy such as logrotate!";
- }
- }
- else {
- infoprint
-"Log file $myvar{'log_error'} is empty. Assuming log-rotation. Use --server-log={file} for explicit file";
- return;
- }
- if ( !open( $fh, '<', $myvar{'log_error'} ) ) {
- badprint "Log file $myvar{'log_error'} isn't readable.";
- return;
- }
- goodprint "Log file $myvar{'log_error'} is readable.";
-
- if ( $maxlines * 80 < $size ) {
- seek( $fh, -$maxlines * 80, 2 );
- <$fh>; # discard line fragment
- }
- }
- else {
- badprint "Log file $myvar{'log_error'} doesn't exist";
- return;
- }
-
- my $numLi = 0;
- my $nbWarnLog = 0;
- my $nbErrLog = 0;
- my @lastShutdowns;
- my @lastStarts;
-
- while ( my $logLi = <$fh> ) {
- chomp $logLi;
- $numLi++;
- debugprint "$numLi: $logLi" if $logLi =~ /\[(warning|error)\]/i;
- $nbErrLog++ if $logLi =~ /\[error\]/i;
- $nbWarnLog++ if $logLi =~ /\[warning\]/i;
- push @lastShutdowns, $logLi
- if $logLi =~ /Shutdown complete/ and $logLi !~ /Innodb/i;
- push @lastStarts, $logLi if $logLi =~ /ready for connections/;
- }
- close $fh;
-
- if ( $nbWarnLog > 0 ) {
- badprint "$myvar{'log_error'} contains $nbWarnLog warning(s).";
- push @generalrec, "Check warning line(s) in $myvar{'log_error'} file";
- }
- else {
- goodprint "$myvar{'log_error'} doesn't contain any warning.";
- }
- if ( $nbErrLog > 0 ) {
- badprint "$myvar{'log_error'} contains $nbErrLog error(s).";
- push @generalrec, "Check error line(s) in $myvar{'log_error'} file";
- }
- else {
- goodprint "$myvar{'log_error'} doesn't contain any error.";
- }
-
- infoprint scalar @lastStarts . " start(s) detected in $myvar{'log_error'}";
- my $nStart = 0;
- my $nEnd = 10;
- if ( scalar @lastStarts < $nEnd ) {
- $nEnd = scalar @lastStarts;
- }
- for my $startd ( reverse @lastStarts[ -$nEnd .. -1 ] ) {
- $nStart++;
- infoprint "$nStart) $startd";
- }
- infoprint scalar @lastShutdowns
- . " shutdown(s) detected in $myvar{'log_error'}";
- $nStart = 0;
- $nEnd = 10;
- if ( scalar @lastShutdowns < $nEnd ) {
- $nEnd = scalar @lastShutdowns;
- }
- for my $shutd ( reverse @lastShutdowns[ -$nEnd .. -1 ] ) {
- $nStart++;
- infoprint "$nStart) $shutd";
- }
-
- #exit 0;
-}
-
-sub cve_recommendations {
- subheaderprint "CVE Security Recommendations";
- unless ( defined( $opt{cvefile} ) && $opt{cvefile} ) {
- infoprint "Skipped: --cvefile option not specified";
- return;
- }
- unless ( -f "$opt{cvefile}" ) {
- infoprint "Skipped: CVE file not found ($opt{cvefile})";
- return;
- }
-
-#$mysqlvermajor=10;
-#$mysqlverminor=1;
-#$mysqlvermicro=17;
-#prettyprint "Look for related CVE for $myvar{'version'} or lower in $opt{cvefile}";
- my $cvefound = 0;
- open( my $fh, "<", $opt{cvefile} )
- or die "Can't open $opt{cvefile} for read: $!";
- while ( my $cveline = <$fh> ) {
- my @cve = split( ';', $cveline );
- debugprint
-"Comparing $mysqlvermajor\.$mysqlverminor\.$mysqlvermicro with $cve[1]\.$cve[2]\.$cve[3] : "
- . ( mysql_version_le( $cve[1], $cve[2], $cve[3] ) ? '<=' : '>' );
-
- # Avoid not major/minor version corresponding CVEs
- next
- unless ( int( $cve[1] ) == $mysqlvermajor
- && int( $cve[2] ) == $mysqlverminor );
- if ( int( $cve[3] ) >= $mysqlvermicro ) {
- badprint "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]";
- $result{'CVE'}{'List'}{$cvefound} =
- "$cve[4](<= $cve[1]\.$cve[2]\.$cve[3]) : $cve[6]";
- $cvefound++;
- }
- }
- close $fh or die "Cannot close $opt{cvefile}: $!";
- $result{'CVE'}{'nb'} = $cvefound;
-
- my $cve_warning_notes = "";
- if ( $cvefound == 0 ) {
- goodprint "NO SECURITY CVE FOUND FOR YOUR VERSION";
- return;
- }
- if ( $mysqlvermajor eq 5 and $mysqlverminor eq 5 ) {
- infoprint
- "False positive CVE(s) for MySQL and MariaDB 5.5.x can be found.";
- infoprint "Check carefully each CVE for those particular versions";
- }
- badprint $cvefound . " CVE(s) found for your MySQL release.";
- push( @generalrec,
- $cvefound
- . " CVE(s) found for your MySQL release. Consider upgrading your version !"
- );
-}
-
-sub get_opened_ports {
- my @opened_ports = execute_system_command('netstat -ltn');
- if ($is_win) {
- @opened_ports = grep { /LISTEN/ } execute_system_command('netstat -n');
- }
- @opened_ports = map {
- my $v = $_;
- $v =~ s/^.*:(\d+)\s.*$/$1/;
- $v =~ s/\D//g;
- $v;
- } @opened_ports;
- @opened_ports = sort { $a <=> $b } grep { !/^$/ } @opened_ports;
-
- #debugprint Dumper \@opened_ports;
- $result{'Network'}{'TCP Opened'} = \@opened_ports;
- return @opened_ports;
-}
-
-sub is_open_port {
- my $port = shift;
- if ( grep { /^$port$/ } get_opened_ports ) {
- return 1;
- }
- return 0;
-}
-
-sub get_process_memory {
- return 0 if $is_win; #Windows cmd cannot provide this
- my $pid = shift;
-
- # Linux /proc fallback
- if ( $^O eq 'linux' && -f "/proc/$pid/statm" ) {
- if ( open( my $fh, '<', "/proc/$pid/statm" ) ) {
- my $line = <$fh>;
- close($fh);
- if ( $line =~ /^\d+\s+(\d+)/ ) {
- my $rss_pages = $1;
-
- # Get page size (default to 4096 if uncertain, but usually 4096 on Linux)
- my $pagesize = POSIX::sysconf(POSIX::_SC_PAGESIZE) || 4096;
- debugprint "Memory for PID $pid from /proc: "
- . ( $rss_pages * $pagesize );
- return $rss_pages * $pagesize;
- }
- }
- }
-
- my @mem = execute_system_command("ps -p $pid -o rss");
- return 0 if scalar @mem != 2;
- return $mem[1] * 1024;
-}
-
-sub get_other_process_memory {
- return 0 if ( $opt{tbstat} == 0 );
- return 0 if $is_win; #Windows cmd cannot provide this
- my @procs = execute_system_command('ps eaxo pid,command');
- @procs = map {
- my $v = $_;
- $v =~ s/.*PID.*//;
- $v =~ s/.*mysqld.*//;
- $v =~ s/.*\[.*\].*//;
- $v =~ s/^\s+$//g;
- $v =~ s/.*PID.*CMD.*//;
- $v =~ s/.*systemd.*//;
- $v =~ s/\s*?(\d+)\s*.*/$1/g;
- $v;
- } @procs;
- @procs = remove_cr @procs;
- @procs = remove_empty @procs;
- my $totalMemOther = 0;
- if (@procs) {
- map { $totalMemOther += get_process_memory($_); } @procs;
- }
- return $totalMemOther;
-}
-
-sub get_os_release {
- if ( -f "/etc/lsb-release" ) {
- my @info_release = get_file_contents "/etc/lsb-release";
- my $os_release = $info_release[3];
- $os_release =~ s/.*="//;
- $os_release =~ s/"$//;
- return $os_release;
- }
-
- if ( -f "/etc/system-release" ) {
- my @info_release = get_file_contents "/etc/system-release";
- return $info_release[0];
- }
-
- if ( -f "/etc/os-release" ) {
- my @info_release = get_file_contents "/etc/os-release";
- my $os_release = $info_release[0];
- $os_release =~ s/.*="//;
- $os_release =~ s/"$//;
- return $os_release;
- }
-
- if ( -f "/etc/issue" ) {
- my @info_release = get_file_contents "/etc/issue";
- my $os_release = $info_release[0];
- $os_release =~ s/\s+\\n.*//;
- return $os_release;
- }
- return "Unknown OS release";
-}
-
-sub get_fs_info {
- my @sinfo = execute_system_command("df -P | grep '%'");
- my @iinfo = execute_system_command("df -Pi| grep '%'");
- shift @sinfo;
- shift @iinfo;
-
- foreach my $info (@sinfo) {
-
- #exit(0);
- if ( $info =~ /.*?(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.*)$/ ) {
- next if $5 =~ m{(run|dev|sys|proc|snap|init)};
- if ( $4 > 85 ) {
- badprint "mount point $5 is using $4 % total space ("
- . human_size( $2 * 1024 ) . " / "
- . human_size( $1 * 1024 ) . ")";
- push( @generalrec, "Add some space to $4 mountpoint." );
- }
- else {
- infoprint "mount point $5 is using $4 % total space ("
- . human_size( $2 * 1024 ) . " / "
- . human_size( $1 * 1024 ) . ")";
- }
- $result{'Filesystem'}{'Space Pct'}{$5} = $4;
- $result{'Filesystem'}{'Used Space'}{$5} = $2;
- $result{'Filesystem'}{'Free Space'}{$5} = $3;
- $result{'Filesystem'}{'Total Space'}{$5} = $1;
- }
- }
-
- @iinfo = map {
- my $v = $_;
- $v =~ s/.*\s(\d+)%\s+(.*)/$1\t$2/g;
- $v;
- } @iinfo;
- foreach my $info (@iinfo) {
- next if $info =~ m{(\d+)\t/(run|dev|sys|proc|snap)($|/)};
- if ( $info =~ /(\d+)\t(.*)/ ) {
- if ( $1 > 85 ) {
- badprint "mount point $2 is using $1 % of max allowed inodes";
- push( @generalrec,
-"Cleanup files from $2 mountpoint or reformat your filesystem."
- );
- }
- else {
- infoprint "mount point $2 is using $1 % of max allowed inodes";
- }
- $result{'Filesystem'}{'Inode Pct'}{$2} = $1;
- }
- }
-}
-
-sub get_fs_info_win {
- my @sinfo =
- execute_system_command('wmic logicaldisk get Name,Size,FreeSpace');
-
- foreach my $info (@sinfo) {
- if ( $info =~ /^\s*(\d+)\s+(.*?)\s+(\d+)\s*$/ ) {
- my ( $free, $name, $size ) = ( $1, $2, $3 );
- my $used = $size - $free;
- my $free_pct = int( ( $free / $size ) * 100 );
- my $used_pct = int( ( $used / $size ) * 100 );
- if ( $used_pct > 85 ) {
- badprint "Disk $name is using $used_pct % total space ("
- . human_size($used) . " / "
- . human_size($size) . ")";
- push( @generalrec, "Add some space to DIsk $name." );
- }
- else {
- infoprint "Disk $name is using $used_pct % total space ("
- . human_size($used) . " / "
- . human_size($size) . ")";
- }
- $result{'Filesystem'}{'Space Pct'}{$name} = $used_pct;
- $result{'Filesystem'}{'Used Space'}{$name} = $used;
- $result{'Filesystem'}{'Free Space'}{$name} = $free;
- $result{'Filesystem'}{'Total Space'}{$name} = $size;
- }
- }
-}
-
-sub merge_hash {
- my $h1 = shift;
- my $h2 = shift;
- my %result = {};
- foreach my $substanceref ( $h1, $h2 ) {
- while ( my ( $k, $v ) = each %$substanceref ) {
- next if ( exists $result{$k} );
- $result{$k} = $v;
- }
- }
- return \%result;
-}
-
-sub is_virtual_machine {
- my $prefix = get_transport_prefix();
- if ( $^O eq 'linux' ) {
- if ( $prefix eq '' && open( my $cpuinfo, '<', '/proc/cpuinfo' ) ) {
- my $isVm = 0;
- while (<$cpuinfo>) {
- if ( /^flags.*\ hypervisor / ) { $isVm = 1; last; }
- }
- close $cpuinfo;
- return $isVm;
- }
- my $isVm = execute_system_command(
- "grep -Ec '^flags.*\ hypervisor\ ' /proc/cpuinfo");
- return ( $isVm == 0 ? 0 : 1 );
- }
-
- if ( $^O eq 'freebsd' ) {
- my $isVm = execute_system_command('sysctl -n kern.vm_guest');
- chomp $isVm;
- print "FARK DEBUG isVm=[$isVm]";
- return ( $isVm eq 'none' ? 0 : 1 );
- }
-
- if ($is_win) {
- my $isVM = execute_system_command('systeminfo');
- return ( $isVM =~ /System Model:\s*(Virtual Machine|VMware)/i ? 1 : 0 );
- }
- return 0;
-}
-
-sub infocmd {
- my $cmd = "@_";
- debugprint "CMD: $cmd";
- my @result = execute_system_command($cmd);
- @result = remove_cr @result;
- for my $l (@result) {
- infoprint "$l";
- }
-}
-
-sub infocmd_tab {
- my $cmd = "@_";
- debugprint "CMD: $cmd";
- my @result = execute_system_command($cmd);
- @result = remove_cr @result;
- for my $l (@result) {
- infoprint "\t$l";
- }
-}
-
-sub infocmd_one {
- my $cmd = "@_";
- my @result = execute_system_command("$cmd 2>&1");
- @result = remove_cr @result;
- return join ', ', @result;
-}
-
-sub get_kernel_info {
- my $prefix = get_transport_prefix();
- my @params = (
- 'fs.aio-max-nr', 'fs.aio-nr',
- 'fs.nr_open', 'fs.file-max',
- 'sunrpc.tcp_fin_timeout', 'sunrpc.tcp_max_slot_table_entries',
- 'sunrpc.tcp_slot_table_entries', 'vm.swappiness'
- );
- infoprint "Information about kernel tuning:";
- foreach my $param (@params) {
- if ( $param =~ /^sunrpc/ ) {
- next unless -d "/proc/sys/sunrpc";
- }
- my @res = execute_system_command("sysctl $param 2>/dev/null");
- if ( $? == 0 ) {
- foreach my $l (@res) {
- chomp $l;
- infoprint "\t$l";
- }
- my $val = execute_system_command("sysctl -n $param 2>/dev/null");
- chomp $val;
- $result{'OS'}{'Config'}{$param} = $val;
- }
- }
- my $prefix = get_transport_prefix();
- if ( $prefix eq '' && -f "/proc/sys/vm/swappiness" ) {
- if ( open( my $fh, '<', "/proc/sys/vm/swappiness" ) ) {
- $swappiness = <$fh>;
- close $fh;
- chomp $swappiness;
- }
- }
- if ( !defined $swappiness || $swappiness eq '' ) {
- $swappiness = execute_system_command('sysctl -n vm.swappiness');
- chomp $swappiness;
- }
-
- if ( $swappiness > 10 ) {
- badprint
- "Swappiness is > 10, please consider having a value lower than 10";
- push @generalrec, "setup swappiness lower or equal to 10";
- push @adjvars,
-'vm.swappiness <= 10 (echo 10 > /proc/sys/vm/swappiness) or vm.swappiness=10 in /etc/sysctl.conf';
- }
- else {
- infoprint "Swappiness is < 10.";
- }
-
- # only if /proc/sys/sunrpc exists
- if ( -d "/proc/sys/sunrpc" ) {
- my $tcp_slot_entries = execute_system_command(
- "sysctl -n sunrpc.tcp_slot_table_entries 2>$devnull");
- chomp $tcp_slot_entries;
- if ( $tcp_slot_entries eq '' or $tcp_slot_entries < 100 ) {
- badprint
-"Initial TCP slot entries is < 1M, please consider having a value greater than 100";
- push @generalrec, "setup Initial TCP slot entries greater than 100";
- push @adjvars,
-'sunrpc.tcp_slot_table_entries > 100 (echo 128 > /proc/sys/sunrpc/tcp_slot_table_entries) or sunrpc.tcp_slot_table_entries=128 in /etc/sysctl.conf';
- }
- else {
- infoprint "TCP slot entries is > 100.";
- }
- }
-
- if ( -f "/proc/sys/fs/aio-max-nr" ) {
- if ( execute_system_command('sysctl -n fs.aio-max-nr') < 1000000 ) {
- badprint
-"Max running total of the number of max. events is < 1M, please consider having a value greater than 1M";
- push @generalrec, "setup Max running number events greater than 1M";
- push @adjvars,
-'fs.aio-max-nr > 1M (echo 1048576 > /proc/sys/fs/aio-max-nr) or fs.aio-max-nr=1048576 in /etc/sysctl.conf';
- }
- else {
- infoprint "Max Number of AIO events is > 1M.";
- }
- }
- if ( -f "/proc/sys/fs/nr_open" ) {
- if ( execute_system_command('sysctl -n fs.nr_open') < 1000000 ) {
- badprint
-"Max running total of the number of file open request is < 1M, please consider having a value greater than 1M";
- push @generalrec,
- "setup running number of open request greater than 1M";
- push @adjvars,
-'fs.aio-nr > 1M (echo 1048576 > /proc/sys/fs/nr_open) or fs.nr_open=1048576 in /etc/sysctl.conf';
- }
- else {
- infoprint "Max Number of open file requests is > 1M.";
- }
- }
-}
-
-sub get_system_info {
- my $prefix = get_transport_prefix();
- $result{'OS'}{'Release'} = get_os_release();
- infoprint get_os_release;
- if ( is_docker() || $opt{'container'} ) {
- infoprint "Machine type : Container";
- $result{'OS'}{'Virtual Machine'} = 'YES';
- }
- elsif (is_virtual_machine) {
- infoprint "Machine type : Virtual machine";
- $result{'OS'}{'Virtual Machine'} = 'YES';
- }
- else {
- infoprint "Machine type : Physical machine";
- $result{'OS'}{'Virtual Machine'} = 'NO';
- }
-
- $result{'Network'}{'Connected'} = 'NO';
- if ($is_win) {
- execute_system_command("ping -n 1 ipecho.net > $devnull 2>&1")
- if which( "ping", $ENV{'PATH'} );
- }
- else {
- execute_system_command("ping -c 1 ipecho.net > $devnull 2>&1")
- if which( "ping", $ENV{'PATH'} );
- }
- my $isConnected = $?;
- if ( $isConnected == 0 ) {
- infoprint "Internet : Connected";
- $result{'Network'}{'Connected'} = 'YES';
- }
- else {
- badprint "Internet : Disconnected";
- }
- $result{'OS'}{'NbCore'} = cpu_cores;
- infoprint "Number of Core CPU : " . cpu_cores;
-
- my ( $sysname, $nodename, $release, $version, $machine );
- if ( !$is_win && $prefix eq '' ) {
- ( $sysname, $nodename, $release, $version, $machine ) = POSIX::uname();
- }
-
- $result{'OS'}{'Type'} =
- $is_win ? 'Windows' : ( $prefix eq '' ? $sysname : execute_system_command('uname -o') );
- infoprint "Operating System Type : "
- . ( $is_win ? 'Windows' : ( $prefix eq '' ? $sysname : execute_system_command('uname -o') ) );
-
- $result{'OS'}{'Kernel'} =
- $is_win
- ? execute_system_command('ver')
- : ( $prefix eq '' ? $release : execute_system_command('uname -r') );
- infoprint "Kernel Release : "
- . ( $is_win ? execute_system_command('ver') : ( $prefix eq '' ? $release : execute_system_command('uname -r') ) );
-
- $result{'OS'}{'Hostname'} =
- ( !$is_win && $prefix eq '' ) ? $nodename : Sys::Hostname::hostname();
-
- $result{'Network'}{'Internal Ip'} =
- $is_win
- ? execute_system_command(
-'ipconfig |perl -ne "if (/IPv. Address/) {print s/^.*?([\\d\\.]*)\\s*$/$1/r; exit; }"'
- )
- : execute_system_command('hostname -I');
- infoprint "Hostname : " . ( ( !$is_win && $prefix eq '' ) ? $nodename : Sys::Hostname::hostname() );
- infoprint "Network Cards : ";
-
- if ( which( "ip", $ENV{'PATH'} ) ) {
- infocmd_tab "ip addr | grep -A1 mtu";
- }
- elsif ( which( "ifconfig", $ENV{'PATH'} ) ) {
- infocmd_tab "ifconfig| grep -A1 mtu";
- }
- infoprint "Internal IP : " . ( ( !$is_win && $prefix eq '' ) ? execute_system_command('hostname -I') : infocmd_one "hostname -I" );
- if ( which( "ip", $ENV{'PATH'} ) ) {
- $result{'Network'}{'Internal Ip'} =
- execute_system_command('ip addr | grep -A1 mtu');
- }
- elsif ( which( "ifconfig", $ENV{'PATH'} ) ) {
- $result{'Network'}{'Internal Ip'} =
- execute_system_command('ifconfig| grep -A1 mtu');
- }
- my $httpcli = get_http_cli();
- infoprint "HTTP client found: $httpcli" if defined $httpcli;
-
- my $ext_ip = "";
- if ( defined $httpcli && $httpcli ne '' ) {
- if ( $httpcli =~ /curl$/ ) {
- $ext_ip = infocmd_one "$httpcli -s -m 3 ipecho.net/plain";
- }
- elsif ( $httpcli =~ /wget$/ ) {
- $ext_ip = infocmd_one "$httpcli -q -t 1 -T 3 -q -O - ipecho.net/plain";
- }
- }
- infoprint "External IP : " . $ext_ip;
- $result{'Network'}{'External Ip'} = $ext_ip;
- badprint "External IP : Can't check, no Internet connectivity"
- unless defined($httpcli);
-
- my $ns_str = "";
- if ( $prefix eq '' && open( my $ns_file, '<', '/etc/resolv.conf' ) ) {
- my @ns_list;
- while (<$ns_file>) {
- push @ns_list, $1 if /^\s*nameserver\s+([^\s]+)/;
- }
- close $ns_file;
- $ns_str = join( ', ', @ns_list );
- }
- else {
- $ns_str = infocmd_one "grep 'nameserver' /etc/resolv.conf \| awk '{print \$2}'";
- }
- infoprint "Name Servers : " . $ns_str;
-
- infoprint "Logged In users : ";
- infocmd_tab "who";
- $result{'OS'}{'Logged users'} = execute_system_command('who');
- infoprint "Ram Usages in MB : ";
- infocmd_tab "free -m | grep -v +";
- $result{'OS'}{'Free Memory RAM'} =
- execute_system_command('free -m | grep -v +');
- infoprint "Load Average : ";
- infocmd_tab "top -n 1 -b | grep 'load average:'";
- $result{'OS'}{'Load Average'} =
- execute_system_command("top -n 1 -b | grep 'load average:'");
-
- infoprint "System Uptime : ";
- infocmd_tab "uptime";
- $result{'OS'}{'Uptime'} = execute_system_command('uptime');
-}
-
-sub system_recommendations {
- if ( is_remote eq 1 ) {
- infoprint "Skipping system checks on remote host";
- return;
- }
- return if ( $opt{sysstat} == 0 );
- subheaderprint "System Linux Recommendations";
- my $os = $is_win ? 'windows' : execute_system_command('uname');
- unless ( $os =~ /Linux/i ) {
- infoprint "Skipped due to non Linux server";
- return;
- }
- prettyprint "Look for related Linux system recommendations";
-
- #prettyprint '-'x78;
- get_system_info();
-
- my $nb_cpus = cpu_cores;
- if ( $nb_cpus > 1 ) {
- goodprint "There is at least one CPU dedicated to database server.";
- }
- else {
- badprint
-"There is only one CPU, consider dedicated one CPU for your database server";
- push_recommendation( 'System',
- "Consider increasing number of CPU for your database server" );
- }
-
- if ( $physical_memory >= 1.5 * 1024 * 1024 * 1024 ) {
- goodprint "There is at least 1.5 Gb of RAM dedicated to Linux server.";
- }
- else {
- badprint
-"There is less than 1,5 Gb of RAM, consider dedicated 1 Gb for your Linux server";
- push_recommendation( 'System',
- "Consider increasing 1,5 / 2 Gb of RAM for your Linux server" );
- }
-
- my $omem = get_other_process_memory;
- infoprint "User process except mysqld used "
- . hr_bytes_rnd($omem) . " RAM.";
- if ( ( 0.15 * $physical_memory ) < $omem ) {
- if ( $opt{nondedicated} ) {
- infoprint "No warning with --nondedicated option";
- infoprint
-"Other user process except mysqld used more than 15% of total physical memory "
- . percentage( $omem, $physical_memory ) . "% ("
- . hr_bytes_rnd($omem) . " / "
- . hr_bytes_rnd($physical_memory) . ")";
- }
- else {
-
- badprint
-"Other user process except mysqld used more than 15% of total physical memory "
- . percentage( $omem, $physical_memory ) . "% ("
- . hr_bytes_rnd($omem) . " / "
- . hr_bytes_rnd($physical_memory) . ")";
- push( @generalrec,
-"Consider stopping or dedicate server for additional process other than mysqld."
- );
- push( @adjvars,
-"DON'T APPLY SETTINGS BECAUSE THERE ARE TOO MANY PROCESSES RUNNING ON THIS SERVER. OOM KILL CAN OCCUR!"
- );
- }
- }
- else {
- infoprint
-"Other user process except mysqld used less than 15% of total physical memory "
- . percentage( $omem, $physical_memory ) . "% ("
- . hr_bytes_rnd($omem) . " / "
- . hr_bytes_rnd($physical_memory) . ")";
- }
-
- if ( $opt{'maxportallowed'} > 0 ) {
- my @opened_ports = get_opened_ports;
- infoprint "There is "
- . scalar @opened_ports
- . " listening port(s) on this server.";
- if ( scalar(@opened_ports) > $opt{'maxportallowed'} ) {
- badprint "There are too many listening ports: "
- . scalar(@opened_ports)
- . " opened > "
- . $opt{'maxportallowed'}
- . "allowed.";
- push( @generalrec,
-"Consider dedicating a server for your database installation with fewer services running on it!"
- );
- }
- else {
- goodprint "There are less than "
- . $opt{'maxportallowed'}
- . " opened ports on this server.";
- }
- }
-
- foreach my $banport (@banned_ports) {
- if ( is_open_port($banport) ) {
- badprint "Banned port: $banport is opened..";
- push( @generalrec,
-"Port $banport is opened. Consider stopping the program over this port."
- );
- }
- else {
- goodprint "$banport is not opened.";
- }
- }
-
- subheaderprint "Filesystem Linux Recommendations";
- if ($is_win) {
- get_fs_info_win;
- }
- else {
- get_fs_info;
- if ( !is_docker() && $opt{'container'} eq '' ) {
- subheaderprint "Kernel Information Recommendations";
- get_kernel_info;
- }
- }
-}
-
-# ---------------------------------------------------------------------------
-# SSL/TLS Security Recommendations
-# ---------------------------------------------------------------------------
-sub ssl_tls_recommendations {
- subheaderprint "SSL/TLS Security Recommendations";
-
-# Check current session encryption
-# Ssl_cipher session status variable tells us if the current connection is encrypted.
- my $session_ssl = select_one("SHOW SESSION STATUS LIKE 'Ssl_cipher'");
- if ( $session_ssl =~ /Ssl_cipher\s+(.*)/ ) {
- my $cipher = $1;
- $cipher =~ s/^\s+|\s+$//g;
- if ( $cipher eq "" || $cipher eq "NULL" || $cipher eq "0" ) {
- badprint "Current connection is NOT encrypted!";
- push_recommendation( 'Security',
-"Current connection is NOT encrypted! Consider using SSL for all connections."
- );
- }
- else {
- goodprint "Current connection is encrypted ($cipher)";
- }
- }
-
- # Global SSL check
- if ( defined( $myvar{'have_ssl'} ) ) {
- if ( $myvar{'have_ssl'} eq 'DISABLED' ) {
- badprint "SSL is DISABLED on the server.";
- push_recommendation( 'Security',
- "Enable SSL support on the server (check have_ssl variable)." );
- }
- elsif ( $myvar{'have_ssl'} eq 'YES' || $myvar{'have_ssl'} eq 'ON' ) {
- goodprint "SSL support is enabled";
- }
- }
-
- # require_secure_transport (MySQL 5.7+, MariaDB 10.5+)
- if ( defined( $myvar{'require_secure_transport'} ) ) {
- if ( $myvar{'require_secure_transport'} eq 'OFF' ) {
- badprint "require_secure_transport is OFF";
- push_recommendation( 'Security',
-"Enable require_secure_transport to force all connections to use SSL."
- );
- }
- else {
- goodprint "require_secure_transport is ON";
- }
- }
-
- # TLS Versions (MySQL 8.0+, MariaDB 10.4.6+)
- if ( defined( $myvar{'tls_version'} ) ) {
- my $tls_versions = $myvar{'tls_version'};
- if ( $tls_versions =~ /TLSv1\.0/i || $tls_versions =~ /TLSv1\.1/i ) {
- badprint "Insecure TLS versions enabled: $tls_versions";
- push_recommendation( 'Security',
- "Disable TLSv1.0 and TLSv1.1. Use only TLSv1.2 or TLSv1.3." );
- }
- else {
- goodprint "Only secure TLS versions enabled: $tls_versions";
- }
- }
-
- # missing certificates
- if ( ( $myvar{'ssl_cert'} || "" ) eq ""
- && ( $myvar{'ssl_key'} || "" ) eq "" )
- {
- badprint "No SSL certificates configured (ssl_cert/ssl_key are empty)";
- push_recommendation( 'Security',
-"Configure SSL certificates (ssl_cert, ssl_key, ssl_ca) to enable encrypted connections."
- );
- }
-}
-
-sub security_recommendations {
- subheaderprint "Security Recommendations";
-
- infoprint "$myvar{'version_comment'} - $myvar{'version'}";
-
- my $PASS_COLUMN_NAME = get_password_column_name();
-
- if ( $PASS_COLUMN_NAME eq '' ) {
- infoprint "Skipped due to none of known auth columns exists";
- return;
- }
- debugprint "Password column = $PASS_COLUMN_NAME";
-
- # IS THERE A ROLE COLUMN
- my $is_role_column = select_one
-"select count(*) from information_schema.columns where TABLE_NAME='user' AND TABLE_SCHEMA='mysql' and COLUMN_NAME='IS_ROLE'";
-
- my $extra_user_condition = "";
- $extra_user_condition = "IS_ROLE = 'N' AND" if $is_role_column > 0;
- my @mysqlstatlist;
- if ( $is_role_column > 0 ) {
- @mysqlstatlist = select_array
-"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE IS_ROLE='Y'";
- foreach my $line ( sort @mysqlstatlist ) {
- chomp($line);
- infoprint "User $line is User Role";
- }
- }
- else {
- debugprint "No Role user detected";
- goodprint "No Role user detected";
- }
-
- # Looking for Anonymous users
- @mysqlstatlist = select_array
-"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE $extra_user_condition (TRIM(USER) = '' OR USER IS NULL)";
-
- #debugprint Dumper \@mysqlstatlist;
-
- #exit 0;
- if (@mysqlstatlist) {
- push_recommendation( 'Security',
- "Remove Anonymous User accounts: there are "
- . scalar(@mysqlstatlist)
- . " anonymous accounts." );
- foreach my $line ( sort @mysqlstatlist ) {
- chomp($line);
- badprint "User "
- . $line
- . " is an anonymous account. Remove with DROP USER "
- . $line . ";";
- }
- }
- else {
- goodprint "There are no anonymous accounts for any database users";
- }
-
- if ( $opt{skippassword} eq 1 ) {
- infoprint "Skipped password checks due to --skippassword option";
- return;
- }
-
- if ( mysql_version_le( 5, 1 ) ) {
- badprint "No more password checks for MySQL version <=5.1";
- badprint "MySQL version <=5.1 is deprecated and end of support.";
- return;
- }
-
- # Looking for Empty Password
- if ( mysql_version_ge( 10, 4 ) ) {
- @mysqlstatlist = select_array
-q{SELECT CONCAT(QUOTE(user), '@', QUOTE(host)) FROM mysql.global_priv WHERE
- ( user != ''
- AND JSON_CONTAINS(Priv, '"mysql_native_password"', '$.plugin') AND JSON_CONTAINS(Priv, '""', '$.authentication_string')
- AND NOT JSON_CONTAINS(Priv, 'true', '$.account_locked')
- )};
- }
- else {
- @mysqlstatlist = select_array
-"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE ($PASS_COLUMN_NAME = '' OR $PASS_COLUMN_NAME IS NULL)
- AND user != ''
- /*!50501 AND plugin NOT IN ('auth_socket', 'unix_socket', 'win_socket', 'auth_pam_compat') */
- /*!80000 AND account_locked = 'N' AND password_expired = 'N' */";
- }
- if (@mysqlstatlist) {
- foreach my $line ( sort @mysqlstatlist ) {
- chomp($line);
- badprint "User '" . $line . "' has no password set.";
- push_recommendation( 'Security',
-"Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');"
- );
- }
- }
- else {
- goodprint "All database users have passwords assigned";
- }
-
- if ( mysql_version_ge( 5, 7 ) ) {
- my $valPlugin = select_one(
-"select count(*) from information_schema.plugins where PLUGIN_NAME='validate_password' AND PLUGIN_STATUS='ACTIVE'"
- );
- if ( $valPlugin >= 1 ) {
- infoprint
-"Bug #80860 MySQL 5.7: Avoid testing password when validate_password is activated";
- return;
- }
- }
-
- # Looking for User with user/ uppercase /capitalise user as password
- if ( !mysql_version_ge(8) ) {
- @mysqlstatlist = select_array
-"SELECT CONCAT(QUOTE(user), '\@', QUOTE(host)) FROM mysql.user WHERE user != '' AND (CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(user) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(UPPER(user)) OR CAST($PASS_COLUMN_NAME as Binary) = PASSWORD(CONCAT(UPPER(LEFT(User, 1)), SUBSTRING(User, 2, LENGTH(User)))))";
- if (@mysqlstatlist) {
- foreach my $line ( sort @mysqlstatlist ) {
- chomp($line);
- badprint "User " . $line . " has user name as password.";
- push( @generalrec,
-"Set up a Secure Password for $line user: SET PASSWORD FOR $line = PASSWORD('secure_password');"
- );
- }
- }
- }
-
- @mysqlstatlist = select_array
- "SELECT CONCAT(QUOTE(user), '\@', host) FROM mysql.user WHERE HOST='%'";
- if ( scalar(@mysqlstatlist) > 0 ) {
- if ( $opt{dumpdir} ne '' && $opt{dumpdir} ne '0' ) {
- select_csv_file(
- "$opt{dumpdir}/user_with_general_wildcard.csv",
- "SELECT user, host FROM mysql.user WHERE HOST='%'"
- );
- }
- my $luser = 'user_name';
- if ( scalar(@mysqlstatlist) == 1 ) {
- $luser = ( split /@/, $mysqlstatlist[0] )[0];
- }
- foreach my $line ( sort @mysqlstatlist ) {
- chomp($line);
- badprint "User " . $line
- . " does not specify hostname restrictions.";
- }
- push( @generalrec,
- "Restrict Host for $luser\@'%' to $luser\@LimitedIPRangeOrLocalhost"
- );
- push( @generalrec,
- "RENAME USER $luser\@'%' TO "
- . $luser
- . "\@LimitedIPRangeOrLocalhost;" );
- }
-
- unless ( -f $basic_password_files ) {
- badprint "There is no basic password file list!";
- return;
- }
-
- my @passwords = get_basic_passwords $basic_password_files;
- infoprint "There are "
- . scalar(@passwords)
- . " basic passwords in the list.";
- my $nbins = 0;
- my $passreq;
- if (@passwords) {
- my $nbInterPass = 0;
- my $skip_dict_check = 0;
-
- # Behavioral check for socket authentication or password bypass (issue #875)
- # Testing if any of these passwords work (including random tokens)
- my $target_user = $opt{user} || 'root';
- foreach my $p ( "true", "false",
- "RA-ND-OM-P-ASS-W-ORD-" . int( rand(100000) ) )
- {
- my $check_cmd =
-"$mysqlcmd $mysqllogin -u $target_user -p'$p' -Nrs -e 'select \"mysqld is alive\";' 2>$devnull";
- my $alive_res = execute_system_command($check_cmd);
- if ( $alive_res =~ /mysqld is alive/ ) {
- infoprint
-"Authentication plugin allows access without a valid password for user '$target_user'. Skipping dictionary check.";
- $skip_dict_check = 1;
- last;
- }
- }
-
- unless ($skip_dict_check) {
- foreach my $pass (@passwords) {
- $nbInterPass++;
- last if $nbInterPass > $opt{'max-password-checks'};
- if ( $nbInterPass % 100 == 0 ) {
- select_one("FLUSH HOSTS;");
- }
-
- $pass =~ s/\s//g;
- $pass =~ s/\'/\\\'/g;
- chomp($pass);
-
- if ( !mysql_version_ge(8) ) {
-
- # Looking for User with user/ uppercase /capitalise weak password
- @mysqlstatlist =
- select_array
-"SELECT CONCAT(user, '\@', host) FROM mysql.user WHERE $PASS_COLUMN_NAME = PASSWORD('"
- . $pass
- . "') OR $PASS_COLUMN_NAME = PASSWORD(UPPER('"
- . $pass
- . "')) OR $PASS_COLUMN_NAME = PASSWORD(CONCAT(UPPER(LEFT('"
- . $pass
- . "', 1)), SUBSTRING('"
- . $pass
- . "', 2, LENGTH('"
- . $pass . "'))))";
- debugprint "There are "
- . scalar(@mysqlstatlist)
- . " items.";
- if (@mysqlstatlist) {
- foreach my $line (@mysqlstatlist) {
- chomp($line);
- badprint "User '" . $line
- . "' is using weak password: $pass in a lower, upper or capitalize derivative version.";
-
- push( @generalrec,
-"Set up a Secure Password for $line user: SET PASSWORD FOR '"
- . ( split /@/, $line )[0] . "'\@'"
- . ( split /@/, $line )[1]
- . "' = PASSWORD('secure_password');" );
- $nbins++;
- }
- }
- }
- else {
- # New way to check basic password for MySQL 8.0+
- my $target_user = $opt{user} || 'root';
- my @variants = ( $pass, uc($pass), ucfirst($pass) );
- foreach my $v (@variants) {
- my $check_login = "$mysqllogin -u $target_user -p'$v'";
- my $alive_res = execute_system_command(
-"$mysqlcmd -Nrs -e 'select \"mysqld is alive\";' $check_login 2>$devnull"
- );
- if ( $alive_res =~ /mysqld is alive/ ) {
- badprint
- "User '$target_user' is using weak password: $v";
- push( @generalrec,
-"Set up a Secure Password for $target_user user."
- );
- $nbins++;
- last;
- }
- }
- }
- debugprint "$nbInterPass / " . scalar(@passwords)
- if ( $nbInterPass % 1000 == 0 );
- }
- }
- }
- if ( $nbins > 0 ) {
- push( @generalrec,
- $nbins
- . " user(s) used basic or weak password from basic dictionary." );
- }
-}
-
-sub get_replication_status {
- subheaderprint "Replication Metrics";
- infoprint "Galera Synchronous replication: " . $myvar{'have_galera'};
- if ( scalar( keys %myslaves ) == 0 ) {
- infoprint "No replication slave(s) for this server.";
- }
- else {
- infoprint "This server is acting as master for "
- . scalar( keys %myslaves )
- . " server(s).";
- }
- infoprint "Binlog format: " . $myvar{'binlog_format'};
- infoprint "XA support enabled: " . $myvar{'innodb_support_xa'};
-
- infoprint "Semi synchronous replication Master: "
- . (
- (
- defined( $myvar{'rpl_semi_sync_master_enabled'} )
- or defined( $myvar{'rpl_semi_sync_source_enabled'} )
- )
- ? ( $myvar{'rpl_semi_sync_master_enabled'}
- // $myvar{'rpl_semi_sync_source_enabled'} )
- : 'Not Activated'
- );
- infoprint "Semi synchronous replication Slave: "
- . (
- (
- defined( $myvar{'rpl_semi_sync_slave_enabled'} )
- or defined( $myvar{'rpl_semi_sync_replica_enabled'} )
- )
- ? ( $myvar{'rpl_semi_sync_slave_enabled'}
- // $myvar{'rpl_semi_sync_replica_enabled'} )
- : 'Not Activated'
- );
- if ( scalar( keys %myrepl ) == 0 and scalar( keys %myslaves ) == 0 ) {
- infoprint "This is a standalone server";
- return;
- }
- if ( scalar( keys %myrepl ) == 0 ) {
- infoprint
- "No replication setup for this server or replication not started.";
- return;
- }
-
- $result{'Replication'}{'status'} = \%myrepl;
- my ($io_running) = $myrepl{'Slave_IO_Running'}
- // $myrepl{'Replica_IO_Running'};
- debugprint "IO RUNNING: $io_running ";
- my ($sql_running) = $myrepl{'Slave_SQL_Running'}
- // $myrepl{'Replica_SQL_Running'};
- debugprint "SQL RUNNING: $sql_running ";
-
- my ($seconds_behind_master) = $myrepl{'Seconds_Behind_Master'}
- // $myrepl{'Seconds_Behind_Source'};
- $seconds_behind_master = 1000000 unless defined($seconds_behind_master);
- debugprint "SECONDS : $seconds_behind_master ";
-
- if ( defined($io_running)
- and ( $io_running !~ /yes/i or $sql_running !~ /yes/i ) )
- {
- badprint
- "This replication slave is not running but seems to be configured.";
- }
- if ( defined($io_running)
- && $io_running =~ /yes/i
- && $sql_running =~ /yes/i )
- {
- if ( $myvar{'read_only'} eq 'OFF' ) {
- badprint
-"This replication slave is running with the read_only option disabled.";
- }
- else {
- goodprint
-"This replication slave is running with the read_only option enabled.";
- }
- if ( $seconds_behind_master > 0 ) {
- badprint
-"This replication slave is lagging and slave has $seconds_behind_master second(s) behind master host.";
- }
- else {
- goodprint "This replication slave is up to date with master.";
- }
- }
-
- # Parallel replication checks (MariaDB specific)
- if ( ( $myvar{'version'} =~ /MariaDB/i )
- or ( $myvar{'version_comment'} =~ /MariaDB/i ) )
- {
- my $parallel_threads = $myvar{'slave_parallel_threads'}
- // $myvar{'replica_parallel_threads'} // 0;
- if ( $parallel_threads > 1 ) {
- goodprint
- "Parallel replication is enabled with $parallel_threads threads.";
-
- # Check parallel mode for MariaDB 10.5+
- if ( mysql_version_ge( 10, 5 ) ) {
- my $parallel_mode = $myvar{'slave_parallel_mode'}
- // $myvar{'replica_parallel_mode'} // '';
- if ( $parallel_mode eq 'optimistic' ) {
- goodprint
- "Parallel replication mode is set to 'optimistic'.";
- }
- else {
- badprint
-"Parallel replication mode is not 'optimistic' (recommended for MariaDB 10.5+).";
- push( @adjvars, "replica_parallel_mode=optimistic" );
- }
- }
- infoprint
-"Ensure binlog_format=ROW is set on the master for parallel replication to work effectively.";
- }
- else {
- badprint "Parallel replication is disabled.";
- push( @adjvars,
- "replica_parallel_threads (set to number of vCPUs)" );
- }
- }
-}
-
-# https://endoflife.date/mysql
-# https://endoflife.date/mariadb
-sub validate_mysql_version {
- ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) =
- $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/;
- $mysqlverminor ||= 0;
- $mysqlvermicro ||= 0;
-
- prettyprint " ";
-
- if ( mysql_version_eq( 8, 0 )
- or mysql_version_eq( 8, 4 )
- or mysql_version_eq( 9, 5 )
- or mysql_version_eq( 10, 6 )
- or mysql_version_eq( 10, 11 )
- or mysql_version_eq( 11, 4 )
- or mysql_version_eq( 11, 8 ) )
- {
- goodprint "Currently running supported MySQL/MariaDB version "
- . $myvar{'version'} . "(LTS)";
- return;
- }
- else {
- badprint "Your MySQL version "
- . $myvar{'version'}
- . " is EOL software. Upgrade soon!";
- push( @generalrec,
- "You are using an unsupported version for production environments"
- );
- push( @generalrec,
- "Upgrade as soon as possible to a supported version !" );
-
- }
-}
-
-# Checks if MySQL version is equal to (major, minor, micro)
-sub mysql_version_eq {
- my ( $maj, $min, $mic ) = @_;
- my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) =
- $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/;
-
- return int($mysqlvermajor) == int($maj)
- if ( !defined($min) && !defined($mic) );
- return int($mysqlvermajor) == int($maj) && int($mysqlverminor) == int($min)
- if ( !defined($mic) );
- return ( int($mysqlvermajor) == int($maj)
- && int($mysqlverminor) == int($min)
- && int($mysqlvermicro) == int($mic) );
-}
-
-# Checks if MySQL version is greater than equal to (major, minor, micro)
-sub mysql_version_ge {
- my ( $maj, $min, $mic ) = @_;
- $min ||= 0;
- $mic ||= 0;
- my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) =
- $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/;
-
- return
- int($mysqlvermajor) > int($maj)
- || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) > int($min) )
- || ( int($mysqlvermajor) == int($maj)
- && int($mysqlverminor) == int($min)
- && int($mysqlvermicro) >= int($mic) );
-}
-
-# Checks if MySQL version is lower than equal to (major, minor, micro)
-sub mysql_version_le {
- my ( $maj, $min, $mic ) = @_;
- $min ||= 0;
- $mic ||= 0;
- my ( $mysqlvermajor, $mysqlverminor, $mysqlvermicro ) =
- $myvar{'version'} =~ /^(\d+)(?:\.(\d+)|)(?:\.(\d+)|)/;
-
- #infoprint "MySQL version: $mysqlvermajor.$mysqlverminor.$mysqlvermicro";
-
- return
- int($mysqlvermajor) < int($maj)
- || ( int($mysqlvermajor) == int($maj) && int($mysqlverminor) < int($min) )
- || ( int($mysqlvermajor) == int($maj)
- && int($mysqlverminor) == int($min)
- && int($mysqlvermicro) <= int($mic) );
-}
-
-# Checks for 32-bit boxes with more than 2GB of RAM
-my ($arch);
-
-sub check_architecture {
- my $prefix = get_transport_prefix();
- if ( is_remote eq 1 || $prefix ne '' ) {
- infoprint "Skipping architecture check on remote host";
- infoprint "Using default $opt{defaultarch} bits as target architecture";
- $arch = $opt{defaultarch};
- return;
- }
- elsif ( $is_win ) {
- if ( execute_system_command('wmic os get osarchitecture') =~ /64/ ) {
- goodprint "Operating on 64-bit architecture";
- $arch = 64;
- }
- }
- else {
- my ( $sysname, $nodename, $release, $version, $machine );
- if ( $prefix eq '' ) {
- ( $sysname, $nodename, $release, $version, $machine ) = POSIX::uname();
- }
- else {
- $sysname = execute_system_command('uname');
- $machine = execute_system_command('uname -m');
- }
-
- if ( $sysname =~ /SunOS/ ) {
- if ( execute_system_command('isainfo -b') =~ /64/ ) {
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- }
- elsif ( $sysname =~ /AIX/ ) {
- if ( execute_system_command('bootinfo -K') =~ /64/ ) {
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- }
- elsif ( $sysname =~ /NetBSD|OpenBSD/ ) {
- if ( execute_system_command('sysctl -b hw.machine') =~ /64/ ) {
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- }
- elsif ( $sysname =~ /FreeBSD/ ) {
- if ( execute_system_command('sysctl -b hw.machine_arch') =~ /64/ ) {
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- }
- elsif ( $sysname =~ /Darwin/ && $machine =~ /Power Macintosh/ ) {
- # Darwin box.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:57:01 PDT 2009; root:xnu1228.15.4~1/RELEASE_PPC Power Macintosh
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- elsif ( $sysname =~ /Darwin/ && $machine =~ /x86_64/ ) {
- # Darwin gibas.local 12.6.0 Darwin Kernel Version 12.3.0: Sun Jan 6 22:37:10 PST 2013; root:xnu-2050.22.13~1/RELEASE_X86_64 x86_64
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- elsif ( $machine =~ /(64|s390x|x86_64|amd64)/ ) {
- $arch = 64;
- goodprint "Operating on 64-bit architecture";
- }
- }
-}
- else {
- $arch = 32;
- if ( $physical_memory > 2147483648 ) {
- badprint
-"Switch to 64-bit OS - MySQL cannot currently use all of your RAM";
- }
- else {
- goodprint "Operating on 32-bit architecture with less than 2GB RAM";
- }
- }
- $result{'OS'}{'Architecture'} = "$arch bits";
-
-}
-
-# Start up a ton of storage engine counts/statistics
-my ( %enginestats, %enginecount, $fragtables );
-
-sub check_storage_engines {
- subheaderprint "Storage Engine Statistics";
- if ( $opt{skipsize} eq 1 ) {
- infoprint "Skipped due to --skipsize option";
- return;
- }
-
- my $engines;
- if ( mysql_version_ge( 5, 5 ) ) {
- my @engineresults = select_array
-"SELECT ENGINE,SUPPORT FROM information_schema.ENGINES ORDER BY ENGINE ASC";
- foreach my $line (@engineresults) {
- my ( $engine, $engineenabled );
- ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/;
- $result{'Engine'}{$engine}{'Enabled'} = $engineenabled;
- $engines .=
- ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" )
- ? greenwrap "+" . $engine . " "
- : redwrap "-" . $engine . " ";
- }
- }
- elsif ( mysql_version_ge( 5, 1, 5 ) ) {
- my @engineresults = select_array
-"SELECT ENGINE, SUPPORT FROM information_schema.ENGINES WHERE ENGINE NOT IN ('MyISAM', 'MERGE', 'MEMORY') ORDER BY ENGINE";
- foreach my $line (@engineresults) {
- my ( $engine, $engineenabled );
- ( $engine, $engineenabled ) = $line =~ /([a-zA-Z_]*)\s+([a-zA-Z]+)/;
- $result{'Engine'}{$engine}{'Enabled'} = $engineenabled;
- $engines .=
- ( $engineenabled eq "YES" || $engineenabled eq "DEFAULT" )
- ? greenwrap "+" . $engine . " "
- : redwrap "-" . $engine . " ";
- }
- }
- else {
- $engines .=
- ( defined $myvar{'have_archive'} && $myvar{'have_archive'} eq "YES" )
- ? greenwrap "+Archive "
- : redwrap "-Archive ";
- $engines .=
- ( defined $myvar{'have_bdb'} && $myvar{'have_bdb'} eq "YES" )
- ? greenwrap "+BDB "
- : redwrap "-BDB ";
- $engines .=
- ( defined $myvar{'have_federated_engine'}
- && $myvar{'have_federated_engine'} eq "YES" )
- ? greenwrap "+Federated "
- : redwrap "-Federated ";
- $engines .=
- ( defined $myvar{'have_innodb'} && $myvar{'have_innodb'} eq "YES" )
- ? greenwrap "+InnoDB "
- : redwrap "-InnoDB ";
- $engines .=
- ( defined $myvar{'have_isam'} && $myvar{'have_isam'} eq "YES" )
- ? greenwrap "+ISAM "
- : redwrap "-ISAM ";
- $engines .=
- ( defined $myvar{'have_ndbcluster'}
- && $myvar{'have_ndbcluster'} eq "YES" )
- ? greenwrap "+NDBCluster "
- : redwrap "-NDBCluster ";
- }
-
- my @dblist = grep { $_ ne 'lost+found' } select_array "SHOW DATABASES";
-
- $result{'Databases'}{'List'} = [@dblist];
- infoprint "Status: $engines";
- if ( mysql_version_ge( 5, 1, 5 ) ) {
-
-# MySQL 5+ servers can have table sizes calculated quickly from information schema
- my @templist = select_array
-"SELECT ENGINE, SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(ENGINE), SUM(DATA_LENGTH), SUM(INDEX_LENGTH) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND ENGINE IS NOT NULL GROUP BY ENGINE ORDER BY ENGINE ASC;";
-
- my ( $engine, $size, $count, $dsize, $isize );
- foreach my $line (@templist) {
- ( $engine, $size, $count, $dsize, $isize ) =
- $line =~ /([a-zA-Z_]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/;
- debugprint "Engine Found: $engine";
- next unless ( defined($engine) or trim($engine) eq '' );
- $size = 0 unless ( defined($size) or trim($engine) eq '' );
- $isize = 0 unless ( defined($isize) or trim($engine) eq '' );
- $dsize = 0 unless ( defined($dsize) or trim($engine) eq '' );
- $count = 0 unless ( defined($count) or trim($engine) eq '' );
- $enginestats{$engine} = $size;
- $enginecount{$engine} = $count;
- $result{'Engine'}{$engine}{'Table Number'} = $count;
- $result{'Engine'}{$engine}{'Total Size'} = $size;
- $result{'Engine'}{$engine}{'Data Size'} = $dsize;
- $result{'Engine'}{$engine}{'Index Size'} = $isize;
- }
-
- #print Dumper( \%enginestats ) if $opt{debug};
- my $not_innodb = '';
- if ( not defined $result{'Variables'}{'innodb_file_per_table'} ) {
- $not_innodb = "AND NOT ENGINE='InnoDB'";
- }
- elsif ( $result{'Variables'}{'innodb_file_per_table'} eq 'OFF' ) {
- $not_innodb = "AND NOT ENGINE='InnoDB'";
- }
- $result{'Tables'}{'Fragmented tables'} =
- [ select_array
-"SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, CAST(DATA_FREE AS SIGNED) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb"
- ];
- $fragtables = scalar @{ $result{'Tables'}{'Fragmented tables'} };
- if ( $opt{dumpdir} ne '' && $opt{dumpdir} ne '0' ) {
- select_csv_file( "$opt{dumpdir}/fragmented_tables.csv",
-"SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, CAST(DATA_FREE AS SIGNED) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql') AND DATA_LENGTH/1024/1024>100 AND cast(DATA_FREE as signed)*100/(DATA_LENGTH+INDEX_LENGTH+cast(DATA_FREE as signed)) > 10 AND NOT ENGINE='MEMORY' $not_innodb"
- );
- }
-
- }
- 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
- foreach my $db (@dblist) {
- chomp($db);
- if ( $db eq "information_schema"
- or $db eq "performance_schema"
- or $db eq "mysql"
- or $db eq "lost+found" )
- {
- 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 );
- }
- my $cmd = "SHOW TABLE STATUS FROM \\\`$db\\\`";
- if ($is_win) {
- $cmd = "SHOW TABLE STATUS FROM \`$db\`";
- }
- push( @tblist, map { [ (split)[@ixs] ] } select_array $cmd );
- }
-
- # Parse through the table list to generate storage engine counts/statistics
- $fragtables = 0;
- foreach my $tbl (@tblist) {
-
- #debugprint "Data dump " . Dumper(@$tbl) if $opt{debug};
- my ( $engine, $size, $datafree ) = @$tbl;
- next if $engine eq 'NULL' or not defined($engine);
- $size = 0 if $size eq 'NULL' or not defined($size);
- $datafree = 0 if $datafree eq 'NULL' or not defined($datafree);
- if ( defined $enginestats{$engine} ) {
- $enginestats{$engine} += $size;
- $enginecount{$engine} += 1;
- }
- else {
- $enginestats{$engine} = $size;
- $enginecount{$engine} = 1;
- }
- if ( $datafree > 0 ) {
- $fragtables++;
- }
- }
- }
- foreach my $engine ( sort keys %enginestats ) {
- my $size = $enginestats{$engine};
- infoprint "Data in $engine tables: "
- . hr_bytes($size)
- . " (Tables: "
- . $enginecount{$engine} . ")" . "";
- }
-
- # 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";
- 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";
- 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 "MyISAM is enabled, but isn't being used";
- push( @generalrec,
-"Add skip-isam to MySQL configuration to disable MyISAM (MySQL > 4.1.0)"
- );
- }
-
- # Fragmented tables
- if ( $fragtables > 0 ) {
- badprint "Total fragmented tables: $fragtables";
- push @generalrec,
-'Run ALTER TABLE ... FORCE or OPTIMIZE TABLE to defragment tables for better performance';
- my $total_free = 0;
- my $fragmented_tables_csv = "schema,table,free_space_mb,sql\n";
- foreach my $table_line ( @{ $result{'Tables'}{'Fragmented tables'} } ) {
- my ( $table_schema, $table_name, $engine, $data_free ) =
- split /\t/msx, $table_line;
- $data_free = $data_free / 1024 / 1024;
- $total_free += $data_free;
- my $generalrec;
- my $fragmented_tables_sql;
- if ( $engine eq 'InnoDB' ) {
- $fragmented_tables_sql =
- "ALTER TABLE `$table_schema`.`$table_name` FORCE;";
- $generalrec = " $fragmented_tables_sql";
- }
- else {
- $fragmented_tables_sql =
- "OPTIMIZE TABLE `$table_schema`.`$table_name`;";
- $generalrec = " $fragmented_tables_sql";
- }
- $fragmented_tables_csv .=
-"$table_schema,$table_name,$data_free,\"$fragmented_tables_sql\"\n";
- $generalrec .= " -- can free $data_free MiB";
- push @generalrec, $generalrec;
- }
- dump_into_file( 'fragmented_tables.csv', $fragmented_tables_csv );
- push @generalrec,
-"Consider defragmenting $fragtables tables to free up $total_free MiB";
- }
- else {
- goodprint "Total fragmented tables: $fragtables";
- }
-
- # Auto increments
- my %tblist;
-
- # Find the maximum integer
- my $maxint = select_one "SELECT ~0";
- $result{'MaxInt'} = $maxint;
-
-# Now we use a database list, and loop through it to get storage engine stats for tables
- foreach my $db (@dblist) {
- chomp($db);
-
- if ( !$tblist{$db} ) {
- $tblist{$db} = ();
- }
-
- if ( $db eq "information_schema" ) { next; }
- my @ia = ( 0, 10 );
- if ( !mysql_version_ge( 4, 1 ) ) {
-
- # MySQL 3.23/4.0 keeps Data_Length in the 5th (0-based) column
- @ia = ( 0, 9 );
- }
- my $cmd = "SHOW TABLE STATUS FROM \\\`$db\\\`";
- if ($is_win) {
- $cmd = "SHOW TABLE STATUS FROM \`$db\`";
- }
- push( @{ $tblist{$db} }, map { [ (split)[@ia] ] } select_array $cmd );
- }
-
- my @dbnames = keys %tblist;
-
- foreach my $db (@dbnames) {
- foreach my $tbl ( @{ $tblist{$db} } ) {
- my ( $name, $autoincrement ) = @$tbl;
-
- if ( $autoincrement =~ /^\d+?$/ ) {
- my $percent = percentage( $autoincrement, $maxint );
- $result{'PctAutoIncrement'}{"$db.$name"} = $percent;
- if ( $percent >= 75 ) {
- badprint
-"Table '$db.$name' has an autoincrement value near max capacity ($percent%)";
- }
- }
- }
- }
-}
-
-sub dump_into_file {
- my $file = shift;
- my $content = shift;
- if ( -d "$opt{dumpdir}" && $opt{dumpdir} ne '0' ) {
- $file = "$opt{dumpdir}/$file";
- open( FILE, ">$file" ) or die "Can't open $file: $!";
- print FILE $content;
- close FILE;
- infoprint "Data saved to $file";
- }
-}
-
-sub calculations {
- if ( $mystat{'Questions'} < 1 ) {
- badprint "Your server has not answered any queries: cannot continue...";
- exit 2;
- }
-
- #infoprint "====>>>> MySQL version: $myvar{'version'}";
- $myvar{'version'} =~ s/(.+)-.*?$/$1/;
-
- #infoprint "====>>>> MySQL version updated: $myvar{'version'}";
- # Per-thread memory
- $mycalc{'per_thread_buffers'} = 0;
- $mycalc{'per_thread_buffers'} += $myvar{'read_buffer_size'}
- if is_int( $myvar{'read_buffer_size'} );
- $mycalc{'per_thread_buffers'} += $myvar{'read_rnd_buffer_size'}
- if is_int( $myvar{'read_rnd_buffer_size'} );
- $mycalc{'per_thread_buffers'} += $myvar{'sort_buffer_size'}
- if is_int( $myvar{'sort_buffer_size'} );
- $mycalc{'per_thread_buffers'} += $myvar{'thread_stack'}
- if is_int( $myvar{'thread_stack'} );
- $mycalc{'per_thread_buffers'} += $myvar{'join_buffer_size'}
- if is_int( $myvar{'join_buffer_size'} );
- $mycalc{'per_thread_buffers'} += $myvar{'binlog_cache_size'}
- if is_int( $myvar{'binlog_cache_size'} );
- debugprint "per_thread_buffers: $mycalc{'per_thread_buffers'} ("
- . human_size( $mycalc{'per_thread_buffers'} ) . " )";
-
-# Error max_allowed_packet is not included in thread buffers size
-#$mycalc{'per_thread_buffers'} += $myvar{'max_allowed_packet'} if is_int($myvar{'max_allowed_packet'});
-
- # Total per-thread memory
- $mycalc{'total_per_thread_buffers'} =
- $mycalc{'per_thread_buffers'} * $myvar{'max_connections'};
-
- # Max total per-thread memory reached
- $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;
- $mycalc{'server_buffers'} +=
- ( defined $myvar{'aria_pagecache_buffer_size'} )
- ? $myvar{'aria_pagecache_buffer_size'}
- : 0;
-
-# Global memory
-# Max used memory is memory used by MySQL based on Max_used_connections
-# This is the max memory used theoretically calculated with the max concurrent connection number reached by mysql
- $mycalc{'max_used_memory'} =
- $mycalc{'server_buffers'} +
- $mycalc{"max_total_per_thread_buffers"} +
- get_pf_memory();
-
- # + get_gcache_memory();
- $mycalc{'pct_max_used_memory'} =
- percentage( $mycalc{'max_used_memory'}, $physical_memory );
-
-# Total possible memory is memory needed by MySQL based on max_connections
-# This is the max memory MySQL can theoretically used if all connections allowed has opened by mysql
- $mycalc{'max_peak_memory'} =
- $mycalc{'server_buffers'} +
- $mycalc{'total_per_thread_buffers'} +
- get_pf_memory();
-
- # + get_gcache_memory();
- $mycalc{'pct_max_physical_memory'} =
- percentage( $mycalc{'max_peak_memory'}, $physical_memory );
-
- debugprint "Max Used Memory: "
- . hr_bytes( $mycalc{'max_used_memory'} ) . "";
- debugprint "Max Used Percentage RAM: "
- . $mycalc{'pct_max_used_memory'} . "%";
-
- debugprint "Max Peak Memory: "
- . hr_bytes( $mycalc{'max_peak_memory'} ) . "";
- debugprint "Max Peak Percentage RAM: "
- . $mycalc{'pct_max_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'};
-
- # Aborted Connections
- $mycalc{'pct_connections_aborted'} =
- percentage( $mystat{'Aborted_connects'}, $mystat{'Connections'} );
- debugprint "Aborted_connects: " . $mystat{'Aborted_connects'} . "";
- debugprint "Connections: " . $mystat{'Connections'} . "";
- debugprint "pct_connections_aborted: "
- . $mycalc{'pct_connections_aborted'} . "";
-
- # Key buffers
- if ( mysql_version_ge( 4, 1 ) && $myvar{'key_buffer_size'} > 0 ) {
- $mycalc{'pct_key_buffer_used'} = sprintf(
- "%.1f",
- (
- 1 - (
- (
- $mystat{'Key_blocks_unused'} *
- $myvar{'key_cache_block_size'}
- ) / $myvar{'key_buffer_size'}
- )
- ) * 100
- );
- }
- else {
- $mycalc{'pct_key_buffer_used'} = 0;
- }
-
- 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 ( defined $mystat{'Aria_pagecache_read_requests'}
- && $mystat{'Aria_pagecache_read_requests'} > 0 )
- {
- $mycalc{'pct_aria_keys_from_mem'} = sprintf(
- "%.1f",
- (
- 100 - (
- (
- $mystat{'Aria_pagecache_reads'} /
- $mystat{'Aria_pagecache_read_requests'}
- ) * 100
- )
- )
- );
- }
- else {
- $mycalc{'pct_aria_keys_from_mem'} = 0;
- }
-
- if ( $mystat{'Key_write_requests'} > 0 ) {
- $mycalc{'pct_wkeys_from_mem'} = sprintf( "%.1f",
- ( ( $mystat{'Key_writes'} / $mystat{'Key_write_requests'} ) * 100 )
- );
- }
- else {
- $mycalc{'pct_wkeys_from_mem'} = 0;
- }
-
- if ( $doremote eq 0 and !mysql_version_ge(5) ) {
- if ($is_win) {
- my $size = 0;
- my @allfiles =
- execute_system_command("dir /-c /s $myvar{'datadir'}");
- foreach (
- map { /^\s*\d+\/\S+\s+\S+\s+(A|P)M\s+(\d+)\s/i; $2 }
- grep { /\.MYI$/i } @allfiles
- )
- {
- $size += $_;
- }
- $mycalc{'total_myisam_indexes'} = $size;
- $size = 0;
- foreach (
- map { /^\s*\d+\/\S+\s+\S+\s+(A|P)M\s+(\d+)\s/i; $2 }
- grep { /\.MAI$/i } @allfiles
- )
- {
- $size += $_;
- }
- $mycalc{'total_aria_indexes'} = $size;
- }
- else {
- my $size = 0;
- $size += (split)[0]
- for execute_system_command(
-"find '$myvar{'datadir'}' -name '*.MYI' -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1"
- );
- $mycalc{'total_myisam_indexes'} = $size;
- $size = 0 + (split)[0]
- for execute_system_command(
-"find '$myvar{'datadir'}' -name '*.MAI' -print0 2>&1 | xargs $xargsflags -0 du -L $duflags 2>&1"
- );
- $mycalc{'total_aria_indexes'} = $size;
- }
- }
- elsif ( mysql_version_ge(5) ) {
- $mycalc{'total_myisam_indexes'} = select_one
-"SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'MyISAM';";
- $mycalc{'total_aria_indexes'} = select_one
-"SELECT IFNULL(SUM(INDEX_LENGTH), 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('information_schema') AND ENGINE = 'Aria';";
- }
- if ( defined $mycalc{'total_myisam_indexes'} ) {
- chomp( $mycalc{'total_myisam_indexes'} );
- }
- if ( defined $mycalc{'total_aria_indexes'} ) {
- chomp( $mycalc{'total_aria_indexes'} );
- }
-
- # Query cache
- if ( mysql_version_ge(8) and mysql_version_le(10) ) {
- $mycalc{'query_cache_efficiency'} = 0;
- }
- elsif ( 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'}
- ) * 100
- );
- }
- else {
- $mycalc{'pct_temp_disk'} = 0;
- }
- }
-
- # Table cache
- if ( $mystat{'Opened_tables'} > 0 ) {
- if ( not defined( $mystat{'Table_open_cache_hits'} ) ) {
- $mycalc{'table_cache_hit_rate'} =
- int( $mystat{'Open_tables'} * 100 / $mystat{'Opened_tables'} );
- }
- else {
- $mycalc{'table_cache_hit_rate'} = int(
- $mystat{'Table_open_cache_hits'} * 100 / (
- $mystat{'Table_open_cache_hits'} +
- $mystat{'Table_open_cache_misses'}
- )
- );
- }
- }
- 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
- $myvar{'innodb_log_files_in_group'} = 1
- unless defined( $myvar{'innodb_log_files_in_group'} );
- $myvar{'innodb_log_files_in_group'} = 1
- if $myvar{'innodb_log_files_in_group'} == 0;
-
- $myvar{"innodb_buffer_pool_instances"} = 1
- unless defined( $myvar{'innodb_buffer_pool_instances'} );
- if ( $myvar{'have_innodb'} eq "YES" ) {
- if ( defined $myvar{'innodb_redo_log_capacity'} ) {
- $mycalc{'innodb_log_size_pct'} =
- ( $myvar{'innodb_redo_log_capacity'} /
- $myvar{'innodb_buffer_pool_size'} ) * 100;
- }
- else {
- $mycalc{'innodb_log_size_pct'} = 0;
- if ( defined $myvar{'innodb_log_file_size'}
- && $myvar{'innodb_log_file_size'} ne ''
- && defined $myvar{'innodb_buffer_pool_size'}
- && $myvar{'innodb_buffer_pool_size'} ne ''
- && $myvar{'innodb_buffer_pool_size'} != 0 )
- {
- $mycalc{'innodb_log_size_pct'} =
- ( $myvar{'innodb_log_file_size'} *
- $myvar{'innodb_log_files_in_group'} * 100 /
- $myvar{'innodb_buffer_pool_size'} );
- }
- }
- }
- if ( !defined $myvar{'innodb_buffer_pool_size'} ) {
- $mycalc{'innodb_log_size_pct'} = 0;
- $myvar{'innodb_buffer_pool_size'} = 0;
- }
-
- # InnoDB Buffer pool read cache efficiency
- (
- $mystat{'Innodb_buffer_pool_read_requests'},
- $mystat{'Innodb_buffer_pool_reads'}
- )
- = ( 1, 1 )
- unless defined $mystat{'Innodb_buffer_pool_reads'};
- $mycalc{'pct_read_efficiency'} = percentage(
- $mystat{'Innodb_buffer_pool_read_requests'},
- (
- $mystat{'Innodb_buffer_pool_read_requests'} +
- $mystat{'Innodb_buffer_pool_reads'}
- )
- ) if defined $mystat{'Innodb_buffer_pool_read_requests'};
- debugprint "pct_read_efficiency: " . $mycalc{'pct_read_efficiency'} . "";
- debugprint "Innodb_buffer_pool_reads: "
- . $mystat{'Innodb_buffer_pool_reads'} . "";
- debugprint "Innodb_buffer_pool_read_requests: "
- . $mystat{'Innodb_buffer_pool_read_requests'} . "";
-
- # InnoDB log write cache efficiency
- ( $mystat{'Innodb_log_write_requests'}, $mystat{'Innodb_log_writes'} ) =
- ( 1, 1 )
- unless defined $mystat{'Innodb_log_writes'};
- $mycalc{'pct_write_efficiency'} = percentage(
- ( $mystat{'Innodb_log_write_requests'} - $mystat{'Innodb_log_writes'} ),
- $mystat{'Innodb_log_write_requests'}
- ) if defined $mystat{'Innodb_log_write_requests'};
- debugprint "pct_write_efficiency: " . $mycalc{'pct_write_efficiency'} . "";
- debugprint "Innodb_log_writes: " . $mystat{'Innodb_log_writes'} . "";
- debugprint "Innodb_log_write_requests: "
- . $mystat{'Innodb_log_write_requests'} . "";
- $mycalc{'pct_innodb_buffer_used'} = percentage(
- (
- $mystat{'Innodb_buffer_pool_pages_total'} -
- $mystat{'Innodb_buffer_pool_pages_free'}
- ),
- $mystat{'Innodb_buffer_pool_pages_total'}
- ) if defined $mystat{'Innodb_buffer_pool_pages_total'};
-
- my $lreq =
- "select ROUND( 100* sum(allocated)/ "
- . $myvar{'innodb_buffer_pool_size'}
- . ',1) FROM sys.x\$innodb_buffer_stats_by_table;';
- debugprint("lreq: $lreq");
- $mycalc{'innodb_buffer_alloc_pct'} = select_one($lreq)
- if ( $opt{experimental} );
-
- # Binlog Cache
- if ( $myvar{'log_bin'} ne 'OFF' ) {
- $mycalc{'pct_binlog_cache'} = percentage(
- $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'},
- $mystat{'Binlog_cache_use'} );
- }
-}
-
-sub mysql_stats {
- subheaderprint "Performance Metrics";
-
- # Show uptime, queries per second, connections, traffic stats
- my $qps;
- if ( $mystat{'Uptime'} > 0 ) {
- $qps = sprintf( "%.3f", $mystat{'Questions'} / $mystat{'Uptime'} );
- }
- push( @generalrec,
-"MySQL was started within the last 24 hours: recommendations may be inaccurate"
- ) if ( $mystat{'Uptime'} < 86400 );
- infoprint "Up for: "
- . pretty_uptime( $mystat{'Uptime'} ) . " ("
- . hr_num( $mystat{'Questions'} ) . " q ["
- . hr_num($qps)
- . " qps], "
- . hr_num( $mystat{'Connections'} )
- . " conn," . " TX: "
- . hr_bytes_rnd( $mystat{'Bytes_sent'} )
- . ", RX: "
- . hr_bytes_rnd( $mystat{'Bytes_received'} ) . ")";
- infoprint "Reads / Writes: "
- . $mycalc{'pct_reads'} . "% / "
- . $mycalc{'pct_writes'} . "%";
-
- # Binlog Cache
- if ( $myvar{'log_bin'} eq 'OFF' ) {
- infoprint "Binary logging is disabled";
- }
- else {
- infoprint "Binary logging is enabled (GTID MODE: "
- . ( defined( $myvar{'gtid_mode'} ) ? $myvar{'gtid_mode'} : "OFF" )
- . ")";
- }
-
- # Memory usage
- infoprint "Physical Memory : " . hr_bytes($physical_memory);
- infoprint "Max MySQL memory : " . hr_bytes( $mycalc{'max_peak_memory'} );
- infoprint "Other process memory: " . hr_bytes( get_other_process_memory() );
-
- infoprint "Total buffers: "
- . hr_bytes( $mycalc{'server_buffers'} )
- . " global + "
- . hr_bytes( $mycalc{'per_thread_buffers'} )
- . " per thread ($myvar{'max_connections'} max threads)";
- infoprint "Performance_schema Max memory usage: "
- . hr_bytes_rnd( get_pf_memory() );
- $result{'Performance_schema'}{'memory'} = get_pf_memory();
- $result{'Performance_schema'}{'pretty_memory'} =
- hr_bytes_rnd( get_pf_memory() );
- infoprint "Galera GCache Max memory usage: "
- . hr_bytes_rnd( get_gcache_memory() );
- $result{'Galera'}{'GCache'}{'memory'} = get_gcache_memory();
- $result{'Galera'}{'GCache'}{'pretty_memory'} =
- hr_bytes_rnd( get_gcache_memory() );
-
- if ( $opt{buffers} ne 0 ) {
- infoprint "Global Buffers";
- infoprint " +-- Key Buffer: "
- . hr_bytes( $myvar{'key_buffer_size'} ) . "";
- infoprint " +-- Max Tmp Table: "
- . hr_bytes( $mycalc{'max_tmp_table_size'} ) . "";
-
- if ( defined $myvar{'query_cache_type'} ) {
- infoprint "Query Cache Buffers";
- infoprint " +-- Query Cache: "
- . $myvar{'query_cache_type'} . " - "
- . (
- $myvar{'query_cache_type'} eq 0 |
- $myvar{'query_cache_type'} eq 'OFF' ? "DISABLED"
- : (
- $myvar{'query_cache_type'} eq 1 ? "ALL REQUESTS"
- : "ON DEMAND"
- )
- ) . "";
- infoprint " +-- Query Cache Size: "
- . hr_bytes( $myvar{'query_cache_size'} ) . "";
- }
-
- infoprint "Per Thread Buffers";
- infoprint " +-- Read Buffer: "
- . hr_bytes( $myvar{'read_buffer_size'} ) . "";
- infoprint " +-- Read RND Buffer: "
- . hr_bytes( $myvar{'read_rnd_buffer_size'} ) . "";
- infoprint " +-- Sort Buffer: "
- . hr_bytes( $myvar{'sort_buffer_size'} ) . "";
- infoprint " +-- Thread stack: "
- . hr_bytes( $myvar{'thread_stack'} ) . "";
- infoprint " +-- Join Buffer: "
- . hr_bytes( $myvar{'join_buffer_size'} ) . "";
- if ( $myvar{'log_bin'} ne 'OFF' ) {
- infoprint "Binlog Cache Buffers";
- infoprint " +-- Binlog Cache: "
- . hr_bytes( $myvar{'binlog_cache_size'} ) . "";
- }
- }
-
- if ( $arch
- && $arch == 32
- && $mycalc{'max_used_memory'} > 2 * 1024 * 1024 * 1024 )
- {
- badprint
- "Allocating > 2GB RAM on 32-bit systems can cause system instability";
- badprint "Maximum reached memory usage: "
- . hr_bytes( $mycalc{'max_used_memory'} )
- . " ($mycalc{'pct_max_used_memory'}% of installed RAM)";
- }
- elsif ( $mycalc{'pct_max_used_memory'} > 85 ) {
- badprint "Maximum reached memory usage: "
- . hr_bytes( $mycalc{'max_used_memory'} )
- . " ($mycalc{'pct_max_used_memory'}% of installed RAM)";
- }
- else {
- goodprint "Maximum reached memory usage: "
- . hr_bytes( $mycalc{'max_used_memory'} )
- . " ($mycalc{'pct_max_used_memory'}% of installed RAM)";
- }
-
- if ( $mycalc{'pct_max_physical_memory'} > 85 ) {
- badprint "Maximum possible memory usage: "
- . hr_bytes( $mycalc{'max_peak_memory'} )
- . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)";
- push( @generalrec,
- "Reduce your overall MySQL memory footprint for system stability" );
- }
- else {
- goodprint "Maximum possible memory usage: "
- . hr_bytes( $mycalc{'max_peak_memory'} )
- . " ($mycalc{'pct_max_physical_memory'}% of installed RAM)";
- }
-
- if ( $physical_memory <
- ( $mycalc{'max_peak_memory'} + get_other_process_memory() ) )
- {
- if ( $opt{nondedicated} ) {
- infoprint "No warning with --nondedicated option";
- infoprint
-"Overall possible memory usage with other process exceeded memory";
- }
- else {
- badprint
-"Overall possible memory usage with other process exceeded memory";
- push( @generalrec,
- "Dedicate this server to your database for highest performance."
- );
- }
- }
- else {
- goodprint
-"Overall possible memory usage with other process is compatible with memory available";
- }
-
- # Slow queries
- if ( $mycalc{'pct_slow_queries'} > 5 ) {
- badprint "Slow queries: $mycalc{'pct_slow_queries'}% ("
- . hr_num( $mystat{'Slow_queries'} ) . "/"
- . hr_num( $mystat{'Questions'} ) . ")";
- }
- else {
- goodprint "Slow queries: $mycalc{'pct_slow_queries'}% ("
- . hr_num( $mystat{'Slow_queries'} ) . "/"
- . hr_num( $mystat{'Questions'} ) . ")";
- }
- 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'})";
- 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'})";
- }
-
- # Aborted Connections
- if ( $mycalc{'pct_connections_aborted'} > 3 ) {
- badprint
-"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})";
- push( @generalrec,
- "Reduce or eliminate unclosed connections and network issues" );
- }
- else {
- goodprint
-"Aborted connections: $mycalc{'pct_connections_aborted'}% ($mystat{'Aborted_connects'}/$mystat{'Connections'})";
- }
-
- # name resolution
- debugprint "skip name resolve: $result{'Variables'}{'skip_name_resolve'}"
- if ( defined( $result{'Variables'}{'skip_name_resolve'} ) );
- if ( defined( $result{'Variables'}{'skip_networking'} )
- && $result{'Variables'}{'skip_networking'} eq 'ON' )
- {
- infoprint
-"Skipped name resolution test due to skip_networking=ON in system variables.";
- }
- elsif ( not defined( $result{'Variables'}{'skip_name_resolve'} ) ) {
- infoprint
-"Skipped name resolution test due to missing skip_name_resolve in system variables.";
- }
-
- # Cpanel and Skip name resolve (Issue #863)
- # Ref: https://support.cpanel.net/hc/en-us/articles/21664293830423
- elsif (-r "/usr/local/cpanel/cpanel"
- || -r "/var/cpanel/cpanel.config"
- || -r "/etc/cpupdate.conf" )
- {
- if ( $result{'Variables'}{'skip_name_resolve'} ne 'OFF'
- and $result{'Variables'}{'skip_name_resolve'} ne '0' )
- {
- badprint
-"cPanel/Flex system detected: skip-name-resolve should be disabled (OFF)";
- push( @generalrec,
-"cPanel recommends keeping skip-name-resolve disabled: https://support.cpanel.net/hc/en-us/articles/21664293830423"
- );
- }
- }
- elsif ( $result{'Variables'}{'skip_name_resolve'} ne 'ON'
- and $result{'Variables'}{'skip_name_resolve'} ne '1' )
- {
- badprint
-"Name resolution is active: a reverse name resolution is made for each new connection which can reduce performance";
- push( @generalrec,
-"Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=ON"
- );
- push( @adjvars, "skip-name-resolve=ON" );
- }
-
- # 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 ( mysql_version_ge(8) and mysql_version_le( 9, 9 ) ) {
- infoprint "Query cache has been removed since MySQL 8.0";
-
- #return;
- }
- elsif ($myvar{'query_cache_size'} < 1
- or $myvar{'query_cache_type'} eq "OFF" )
- {
- goodprint
-"Query cache is disabled by default due to mutex contention on multiprocessor machines.";
- }
- elsif ( $mystat{'Com_select'} == 0 ) {
- badprint
- "Query cache cannot be analyzed: no SELECT statements executed";
- }
- 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)";
- badprint
- "Query cache may be disabled by default due to mutex contention.";
- push( @adjvars, "query_cache_size (=0)" );
- push( @adjvars, "query_cache_type (=0)" );
- }
- else {
- goodprint
- "Query cache efficiency: $mycalc{'query_cache_efficiency'}% ("
- . hr_num( $mystat{'Qcache_hits'} )
- . " cached / "
- . hr_num( $mystat{'Qcache_hits'} + $mystat{'Com_select'} )
- . " selects)";
- if ( $mycalc{'query_cache_prunes_per_day'} > 98 ) {
- badprint
-"Query cache prunes per day: $mycalc{'query_cache_prunes_per_day'}";
- 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'}";
- }
- }
-
- }
-
- # Sorting
- if ( $mycalc{'total_sorts'} == 0 ) {
- goodprint "No Sort requiring temporary tables";
- }
- 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)";
- 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)";
- }
-
- # Joins
- if ( $mycalc{'joins_without_indexes_per_day'} > 250 ) {
- badprint
- "Joins performed without indexes: $mycalc{'joins_without_indexes'}";
- if ( $myvar{'join_buffer_size'} < 4 * 1024 * 1024 ) {
- push( @adjvars,
- "join_buffer_size (> "
- . hr_bytes( $myvar{'join_buffer_size'} )
- . ", or always use indexes with JOINs)" );
- }
- else {
- push( @adjvars, "always use indexes with JOINs" );
- }
- push(
- @generalrec,
-"We will suggest raising the 'join_buffer_size' until JOINs not using indexes are found.
- See https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_join_buffer_size"
- );
- }
- else {
- goodprint "No joins without indexes";
-
- # 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_tables'} )
- . " total)";
- 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 which have no LIMIT clause"
- );
- }
- elsif ($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_tables'} )
- . " total)";
- 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_tables'} )
- . " total)";
- }
- }
- else {
- goodprint "No tmp tables created on disk";
- }
-
- # Thread cache
- if ( defined( $myvar{'have_threadpool'} )
- and $myvar{'have_threadpool'} eq 'YES' )
- {
-# https://www.percona.com/doc/percona-server/5.7/performance/threadpool.html#status-variables
-# When thread pool is enabled, the value of the thread_cache_size variable
-# is ignored. The Threads_cached status variable contains 0 in this case.
- infoprint "Thread cache not used with thread pool enabled";
- }
- else {
- if ( $myvar{'thread_cache_size'} eq 0 ) {
- badprint "Thread cache is disabled";
- 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)";
- 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)";
- }
- }
- }
-
- # Table cache
- my $table_cache_var = "";
- if ( $mystat{'Open_tables'} > 0 ) {
- if ( $mycalc{'table_cache_hit_rate'} < 20 ) {
-
- unless ( defined( $mystat{'Table_open_cache_hits'} ) ) {
- badprint
- "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% ("
- . hr_num( $mystat{'Open_tables'} )
- . " hits / "
- . hr_num( $mystat{'Opened_tables'} )
- . " requests)";
- }
- else {
- badprint
- "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% ("
- . hr_num( $mystat{'Table_open_cache_hits'} )
- . " hits / "
- . hr_num( $mystat{'Table_open_cache_hits'} +
- $mystat{'Table_open_cache_misses'} )
- . " requests)";
- }
-
- if ( mysql_version_ge( 5, 1 ) ) {
- $table_cache_var = "table_open_cache";
- }
- else {
- $table_cache_var = "table_cache";
- }
-
- push( @adjvars,
- $table_cache_var . " (> " . $myvar{$table_cache_var} . ")" );
- push( @generalrec,
- "Increase "
- . $table_cache_var
- . " gradually to avoid file descriptor limits" );
- push( @generalrec,
- "Read this before increasing "
- . $table_cache_var
- . " over 64: https://bit.ly/2Fulv7r" );
- push( @generalrec,
- "Read this before increasing for MariaDB"
- . " https://mariadb.com/kb/en/library/optimizing-table_open_cache/"
- );
- push( @generalrec,
-"This is MyISAM only table_cache scalability problem, InnoDB not affected."
- );
- push( @generalrec,
- "For more details see: https://bugs.mysql.com/bug.php?id=49177"
- );
- push( @generalrec,
-"This bug already fixed in MySQL 5.7.9 and newer MySQL versions."
- );
- push( @generalrec,
- "Beware that open_files_limit ("
- . $myvar{'open_files_limit'}
- . ") variable " );
- push( @generalrec,
- "should be greater than $table_cache_var ("
- . $myvar{$table_cache_var}
- . ")" );
- }
- else {
- unless ( defined( $mystat{'Table_open_cache_hits'} ) ) {
- goodprint
- "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% ("
- . hr_num( $mystat{'Open_tables'} )
- . " hits / "
- . hr_num( $mystat{'Opened_tables'} )
- . " requests)";
- }
- else {
- goodprint
- "Table cache hit rate: $mycalc{'table_cache_hit_rate'}% ("
- . hr_num( $mystat{'Table_open_cache_hits'} )
- . " hits / "
- . hr_num( $mystat{'Table_open_cache_hits'} +
- $mystat{'Table_open_cache_misses'} )
- . " requests)";
- }
- }
- }
-
- # Table definition cache
- my $nbtables = select_one('SELECT COUNT(*) FROM information_schema.tables');
- $mycalc{'total_tables'} = $nbtables;
- if ( defined $myvar{'table_definition_cache'} ) {
- if ( $myvar{'table_definition_cache'} == -1 ) {
- infoprint( "table_definition_cache ("
- . $myvar{'table_definition_cache'}
- . ") is in autosizing mode" );
- }
- elsif ( $myvar{'table_definition_cache'} < $nbtables ) {
- badprint "table_definition_cache ("
- . $myvar{'table_definition_cache'}
- . ") is less than number of tables ($nbtables) ";
- push( @adjvars,
- "table_definition_cache ("
- . $myvar{'table_definition_cache'} . ") > "
- . $nbtables
- . " or -1 (autosizing if supported)" );
- }
- else {
- goodprint "table_definition_cache ("
- . $myvar{'table_definition_cache'}
- . ") is greater than number of tables ($nbtables)";
- }
- }
- else {
- infoprint "No table_definition_cache variable found.";
- }
-
- # 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'} ) . ")";
- 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'} ) . ")";
- }
- }
-
- # 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'}%";
- 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)";
- }
- }
-
- # Binlog cache
- if ( defined $mycalc{'pct_binlog_cache'} ) {
- if ( $mycalc{'pct_binlog_cache'} < 90
- && $mystat{'Binlog_cache_use'} > 0 )
- {
- badprint "Binlog cache memory access: "
- . $mycalc{'pct_binlog_cache'} . "% ("
- . (
- $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} )
- . " Memory / "
- . $mystat{'Binlog_cache_use'}
- . " Total)";
- push( @generalrec,
- "Increase binlog_cache_size (current value: "
- . $myvar{'binlog_cache_size'}
- . ")" );
- push( @adjvars,
- "binlog_cache_size ("
- . hr_bytes( $myvar{'binlog_cache_size'} + 16 * 1024 * 1024 )
- . ")" );
- }
- else {
- goodprint "Binlog cache memory access: "
- . $mycalc{'pct_binlog_cache'} . "% ("
- . (
- $mystat{'Binlog_cache_use'} - $mystat{'Binlog_cache_disk_use'} )
- . " Memory / "
- . $mystat{'Binlog_cache_use'}
- . " Total)";
- debugprint "Not enough data to validate binlog cache size\n"
- if $mystat{'Binlog_cache_use'} < 10;
- }
- }
-
- # Performance options
- if ( !mysql_version_ge( 5, 1 ) ) {
- push( @generalrec, "Upgrade to MySQL 5.5+ to use asynchronous write" );
- }
- 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" );
- }
-}
-
-# Recommendations for MyISAM
-sub mysql_myisam {
- return 0 unless ( $opt{'myisamstat'} > 0 );
- subheaderprint "MyISAM Metrics";
- my $nb_myisam_tables = select_one(
-"SELECT COUNT(*) FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')"
- );
- push( @generalrec,
- "MyISAM engine is deprecated, consider migrating to InnoDB" )
- if $nb_myisam_tables > 0;
-
- if ( $nb_myisam_tables > 0 ) {
- badprint
- "Consider migrating $nb_myisam_tables following tables to InnoDB:";
- my $sql_mig = "";
- for my $myisam_table (
- select_array(
-"SELECT CONCAT('|',TABLE_SCHEMA, '|.|', TABLE_NAME,'|') FROM information_schema.TABLES WHERE ENGINE='MyISAM' and TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')"
- )
- )
- {
- my $myisam_table_escape = $myisam_table =~ s/\|/\`/gr;
- $sql_mig =
-"${sql_mig}-- InnoDB migration for $myisam_table_escape\nALTER TABLE $myisam_table_escape ENGINE=InnoDB;\n\n";
- infoprint
-"* InnoDB migration request for $myisam_table_escape Table: ALTER TABLE $myisam_table_escape ENGINE=InnoDB;";
- }
- dump_into_file( "migrate_myisam_to_innodb.sql", $sql_mig );
- }
- infoprint("General MyIsam metrics:");
- infoprint " +-- Total MyISAM Tables : $nb_myisam_tables";
- infoprint " +-- Total MyISAM indexes : "
- . hr_bytes( $mycalc{'total_myisam_indexes'} )
- if defined( $mycalc{'total_myisam_indexes'} );
- infoprint " +-- KB Size :" . hr_bytes( $myvar{'key_buffer_size'} );
- infoprint " +-- KB Used Size :"
- . hr_bytes( $myvar{'key_buffer_size'} -
- $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} );
- infoprint " +-- KB used :" . $mycalc{'pct_key_buffer_used'} . "%";
- infoprint " +-- Read KB hit rate: $mycalc{'pct_keys_from_mem'}% ("
- . hr_num( $mystat{'Key_read_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_reads'} )
- . " reads)";
- infoprint " +-- Write KB hit rate: $mycalc{'pct_wkeys_from_mem'}% ("
- . hr_num( $mystat{'Key_write_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_writes'} )
- . " writes)";
-
- if ( $nb_myisam_tables == 0 ) {
- infoprint "No MyISAM table(s) detected ....";
- return;
- }
- if ( mysql_version_ge(8) and mysql_version_le(10) ) {
- infoprint "MyISAM Metrics are disabled since MySQL 8.0.";
- if ( $myvar{'key_buffer_size'} > 0 ) {
- push( @adjvars, "key_buffer_size=0" );
- push( @generalrec,
- "Buffer Key MyISAM set to 0, no MyISAM table detected" );
- }
- return;
- }
-
- if ( !defined( $mycalc{'total_myisam_indexes'} ) ) {
- badprint
- "Unable to calculate MyISAM index size on MySQL server < 5.0.0";
- push( @generalrec,
- "Unable to calculate MyISAM index size on MySQL server < 5.0.0" );
- return;
- }
- if ( $mycalc{'pct_key_buffer_used'} == 0 ) {
-
- # No queries have run that would use keys
- infoprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% ("
- . hr_bytes( $myvar{'key_buffer_size'} -
- $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} )
- . " used / "
- . hr_bytes( $myvar{'key_buffer_size'} )
- . " cache)";
- infoprint "No SQL statement based on MyISAM table(s) detected ....";
- return;
- }
-
- # Key buffer usage
- if ( $mycalc{'pct_key_buffer_used'} < 90 ) {
- badprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% ("
- . hr_bytes( $myvar{'key_buffer_size'} -
- $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} )
- . " used / "
- . hr_bytes( $myvar{'key_buffer_size'} )
- . " cache)";
-
- push(
- @adjvars,
- "key_buffer_size (\~ "
- . hr_num(
- $myvar{'key_buffer_size'} *
- $mycalc{'pct_key_buffer_used'} / 100
- )
- . ")"
- );
- }
- else {
- goodprint "Key buffer used: $mycalc{'pct_key_buffer_used'}% ("
- . hr_bytes( $myvar{'key_buffer_size'} -
- $mystat{'Key_blocks_unused'} * $myvar{'key_cache_block_size'} )
- . " used / "
- . hr_bytes( $myvar{'key_buffer_size'} )
- . " cache)";
- }
-
- # Key buffer size / total MyISAM indexes
- if ( $myvar{'key_buffer_size'} < $mycalc{'total_myisam_indexes'}
- && $mycalc{'pct_keys_from_mem'} < 95
- && $mycalc{'pct_key_buffer_used'} >= 90 )
- {
- badprint "Key buffer size / total MyISAM indexes: "
- . hr_bytes( $myvar{'key_buffer_size'} ) . "/"
- . hr_bytes( $mycalc{'total_myisam_indexes'} ) . "";
- 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'} ) . "";
- }
- if ( $mystat{'Key_read_requests'} > 0 ) {
- if ( $mycalc{'pct_keys_from_mem'} < 95 ) {
- badprint
- "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% ("
- . hr_num( $mystat{'Key_read_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_reads'} )
- . " reads)";
- }
- else {
- goodprint
- "Read Key buffer hit rate: $mycalc{'pct_keys_from_mem'}% ("
- . hr_num( $mystat{'Key_read_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_reads'} )
- . " reads)";
- }
- }
-
- # No queries have run that would use keys
- debugprint "Key buffer size / total MyISAM indexes: "
- . hr_bytes( $myvar{'key_buffer_size'} ) . "/"
- . hr_bytes( $mycalc{'total_myisam_indexes'} ) . "";
- if ( $mystat{'Key_write_requests'} > 0 ) {
- if ( $mycalc{'pct_wkeys_from_mem'} < 95 ) {
- badprint
- "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% ("
- . hr_num( $mystat{'Key_write_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_writes'} )
- . " writes)";
- }
- else {
- goodprint
- "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% ("
- . hr_num( $mystat{'Key_write_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_writes'} )
- . " writes)";
- }
- }
- else {
- # No queries have run that would use keys
- debugprint
- "Write Key buffer hit rate: $mycalc{'pct_wkeys_from_mem'}% ("
- . hr_num( $mystat{'Key_write_requests'} )
- . " cached / "
- . hr_num( $mystat{'Key_writes'} )
- . " writes)";
- }
-}
-
-# Recommendations for ThreadPool
-# See issue #404: https://github.com/jmrenouard/MySQLTuner-perl/issues/404
-sub mariadb_threadpool {
- my $is_mariadb = ( ( $myvar{'version'} // '' ) =~ /mariadb/i );
- my $is_percona = (
- ( $myvar{'version'} // '' ) =~ /percona/i
- or ( $myvar{'version_comment'} // '' ) =~ /percona/i
- );
-
- # Thread Pool is only relevant for MariaDB and Percona
- return unless ( $is_mariadb or $is_percona );
-
- my $thread_handling = $myvar{'thread_handling'}
- // 'one-thread-per-connection';
- my $is_threadpool_enabled = ( $thread_handling eq 'pool-of-threads' );
-
-# Recommendation to ENABLE thread pool if connections are high
-# https://www.percona.com/blog/2014/01/23/percona-server-improve-scalability-percona-thread-pool/
- if ( !$is_threadpool_enabled
- && ( $mystat{'Max_used_connections'} // 0 ) >= 512 )
- {
- subheaderprint "ThreadPool Metrics";
- infoprint "ThreadPool stat is disabled.";
- badprint
- "Max_used_connections ($mystat{'Max_used_connections'}) is >= 512.";
- push( @generalrec,
-"Enabling the thread pool is recommended for servers with max_connections >= 512 (currently $myvar{'max_connections'})"
- );
- push( @adjvars, "thread_handling=pool-of-threads" );
- }
-
- # If it IS enabled, show metrics and recommendations
- if ($is_threadpool_enabled) {
- subheaderprint "ThreadPool Metrics";
- infoprint "ThreadPool stat is enabled.";
- infoprint "Thread Pool Size: "
- . $myvar{'thread_pool_size'}
- . " thread(s).";
-
- # Recommendation to DISABLE thread pool if connections are low
- if ( ( $mystat{'Max_used_connections'} // 0 ) < 512 ) {
- badprint
-"ThreadPool is enabled but Max_used_connections is < 512 ($mystat{'Max_used_connections'}).";
- push( @generalrec,
-"Thread pool is usually only efficient for servers with max_connections >= 512"
- );
- }
-
- my $np = logical_cpu_cores();
- if ( $np <= 0 ) {
- debugprint
-"Unable to detect logical CPU cores for thread_pool_size recommendation.";
- return;
- }
-
-# Percona and MariaDB recommendation: ideally one active thread per CPU
-# Efficient range: [NCPU, NCPU + NCPU/2]
-# Source: https://mariadb.com/kb/en/library/thread-pool-in-mariadb/
-# Source: https://www.percona.com/blog/2014/01/23/percona-server-improve-scalability-percona-thread-pool/
- my $min_tps = $np;
- my $max_tps = int( $np * 1.5 );
-
- if ( $myvar{'thread_pool_size'} >= $min_tps
- && $myvar{'thread_pool_size'} <= $max_tps )
- {
- goodprint
-"thread_pool_size is optimal ($myvar{'thread_pool_size'}) for your $np CPUs (range: $min_tps - $max_tps)";
- }
- else {
- badprint
-"thread_pool_size ($myvar{'thread_pool_size'}) is not in the recommended range [$min_tps, $max_tps] for your $np CPUs.";
- push( @adjvars, "thread_pool_size between $min_tps and $max_tps" );
- }
- }
-}
-
-sub get_pf_memory {
-
- # Performance Schema
- return 0 unless defined $myvar{'performance_schema'};
- return 0 if $myvar{'performance_schema'} eq 'OFF';
-
- my @infoPFSMemory = grep { /\tperformance_schema[.]memory\t/msx }
- select_array("SHOW ENGINE PERFORMANCE_SCHEMA STATUS");
- @infoPFSMemory == 1 || return 0;
- $infoPFSMemory[0] =~ s/.*\s+(\d+)$/$1/g;
- return $infoPFSMemory[0];
-}
-
-# Recommendations for Performance Schema
-sub mysql_pfs {
- return if ( $opt{pfstat} == 0 );
- subheaderprint "Performance schema";
-
- # Performance Schema
- debugprint "Performance schema is " . $myvar{'performance_schema'};
- $myvar{'performance_schema'} = 'OFF'
- unless defined( $myvar{'performance_schema'} );
- if ( $myvar{'performance_schema'} eq 'OFF' ) {
- badprint
- "Performance_schema should be activated (observability issue).";
- push( @adjvars, "performance_schema=ON" );
- push( @generalrec,
-"Performance schema should be activated for better diagnostics and observability"
- );
- }
- if ( $myvar{'performance_schema'} eq 'ON' ) {
- infoprint "Performance_schema is activated.";
- debugprint "Performance schema is " . $myvar{'performance_schema'};
- infoprint "Memory used by Performance_schema: "
- . hr_bytes( get_pf_memory() );
- }
-
- unless ( grep /^sys$/, select_array("SHOW DATABASES") ) {
- infoprint "Sys schema is not installed.";
- push( @generalrec,
- mysql_version_ge( 10, 0 )
- ? "Consider installing Sys schema from https://github.com/FromDual/mariadb-sys for MariaDB"
- : "Consider installing Sys schema from https://github.com/mysql/mysql-sys for MySQL"
- ) unless ( mysql_version_le( 5, 6 ) );
-
- return;
- }
- infoprint "Sys schema is installed.";
- return if ( $opt{pfstat} == 0 or $myvar{'performance_schema'} ne 'ON' );
-
- infoprint "Sys schema Version: "
- . select_one("select sys_version from sys.version");
-
- # Top user per connection
- subheaderprint "Performance schema: Top 5 user per connection";
- my $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, total_connections from sys.user_summary order by total_connections desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery conn(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per statement
- subheaderprint "Performance schema: Top 5 user per statement";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, statements from sys.user_summary order by statements desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery stmt(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per statement latency
- subheaderprint "Performance schema: Top 5 user per statement latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, statement_avg_latency from sys.x\\$user_summary order by statement_avg_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per lock latency
- subheaderprint "Performance schema: Top 5 user per lock latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, lock_latency from sys.x\\$user_summary_by_statement_latency order by lock_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per full scans
- subheaderprint "Performance schema: Top 5 user per nb full scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, full_scans from sys.x\\$user_summary_by_statement_latency order by full_scans desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per row_sent
- subheaderprint "Performance schema: Top 5 user per rows sent";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, rows_sent from sys.x\\$user_summary_by_statement_latency order by rows_sent desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per row modified
- subheaderprint "Performance schema: Top 5 user per rows modified";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, rows_affected from sys.x\\$user_summary_by_statement_latency order by rows_affected desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per io
- subheaderprint "Performance schema: Top 5 user per IO";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, file_ios from sys.x\\$user_summary order by file_ios desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top user per io latency
- subheaderprint "Performance schema: Top 5 user per IO latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, file_io_latency from sys.x\\$user_summary order by file_io_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per connection
- subheaderprint "Performance schema: Top 5 host per connection";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, total_connections from sys.x\\$host_summary order by total_connections desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery conn(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per statement
- subheaderprint "Performance schema: Top 5 host per statement";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, statements from sys.x\\$host_summary order by statements desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery stmt(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per statement latency
- subheaderprint "Performance schema: Top 5 host per statement latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, statement_avg_latency from sys.x\\$host_summary order by statement_avg_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per lock latency
- subheaderprint "Performance schema: Top 5 host per lock latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, lock_latency from sys.x\\$host_summary_by_statement_latency order by lock_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per full scans
- subheaderprint "Performance schema: Top 5 host per nb full scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, full_scans from sys.x\\$host_summary_by_statement_latency order by full_scans desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per rows sent
- subheaderprint "Performance schema: Top 5 host per rows sent";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, rows_sent from sys.x\\$host_summary_by_statement_latency order by rows_sent desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per rows modified
- subheaderprint "Performance schema: Top 5 host per rows modified";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, rows_affected from sys.x\\$host_summary_by_statement_latency order by rows_affected desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per io
- subheaderprint "Performance schema: Top 5 host per io";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, file_ios from sys.x\\$host_summary order by file_ios desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top 5 host per io latency
- subheaderprint "Performance schema: Top 5 host per io latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, file_io_latency from sys.x\\$host_summary order by file_io_latency desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top IO type order by total io
- subheaderprint "Performance schema: Top IO type order by total io";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select substring(event_name,14), SUM(total)AS total from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery i/o";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top IO type order by total latency
- subheaderprint "Performance schema: Top IO type order by total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select substring(event_name,14), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY total_latency DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top IO type order by max latency
- subheaderprint "Performance schema: Top IO type order by max latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select substring(event_name,14), MAX(max_latency) as max_latency from sys.x\\$host_summary_by_file_io_type GROUP BY substring(event_name,14) ORDER BY max_latency DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top Stages order by total io
- subheaderprint "Performance schema: Top Stages order by total io";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select substring(event_name,7), SUM(total)AS total from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery i/o";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top Stages order by total latency
- subheaderprint "Performance schema: Top Stages order by total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select substring(event_name,7), ROUND(SUM(total_latency),1) AS total_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY total_latency DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top Stages order by avg latency
- subheaderprint "Performance schema: Top Stages order by avg latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select substring(event_name,7), MAX(avg_latency) as avg_latency from sys.x\\$host_summary_by_stages GROUP BY substring(event_name,7) ORDER BY avg_latency DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top host per table scans
- subheaderprint "Performance schema: Top 5 host per table scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select host, table_scans from sys.x\\$host_summary order by table_scans desc LIMIT 5'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # InnoDB Buffer Pool by schema
- subheaderprint "Performance schema: InnoDB Buffer Pool by schema";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select object_schema, allocated, data, pages from sys.x\\$innodb_buffer_stats_by_schema ORDER BY pages DESC'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery page(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # InnoDB Buffer Pool by table
- subheaderprint "Performance schema: 40 InnoDB Buffer Pool by table";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select object_schema, object_name, allocated,data, pages from sys.x\\$innodb_buffer_stats_by_table ORDER BY pages DESC LIMIT 40'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery page(s)";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Process per allocated memory
- subheaderprint "Performance schema: Process per time";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, Command AS PROC, time from sys.x\\$processlist ORDER BY time DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # InnoDB Lock Waits
- subheaderprint "Performance schema: InnoDB Lock Waits";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select wait_age_secs, locked_table, locked_type, waiting_query from sys.x\\$innodb_lock_waits order by wait_age_secs DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Threads IO Latency
- subheaderprint "Performance schema: Thread IO Latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select user, total_latency, max_latency from sys.x\\$io_by_thread_by_latency order by total_latency DESC;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # High Cost SQL statements
- subheaderprint "Performance schema: Top 15 Most latency statements";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select LEFT(query, 120), avg_latency from sys.x\\$statement_analysis order by avg_latency desc LIMIT 15'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top 5% slower queries
- subheaderprint "Performance schema: Top 15 slower queries";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select LEFT(query, 120), exec_count from sys.x\\$statements_with_runtimes_in_95th_percentile order by exec_count desc LIMIT 15'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery s";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top 10 nb statement type
- subheaderprint "Performance schema: Top 15 nb statement type";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(total) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top statement by total latency
- subheaderprint "Performance schema: Top 15 statement by total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(total_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top statement by lock latency
- subheaderprint "Performance schema: Top 15 statement by lock latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(lock_latency) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top statement by full scans
- subheaderprint "Performance schema: Top 15 statement by full scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(full_scans) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top statement by rows sent
- subheaderprint "Performance schema: Top 15 statement by rows sent";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(rows_sent) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Top statement by rows modified
- subheaderprint "Performance schema: Top 15 statement by rows modified";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select statement, sum(rows_affected) as total from sys.x\\$host_summary_by_statement_type group by statement order by total desc LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Use temporary tables
- subheaderprint "Performance schema: 15 sample queries using temp table";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select left(query, 120) from sys.x\\$statements_with_temp_tables LIMIT 15'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Unused Indexes
- subheaderprint "Performance schema: Unused indexes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-"select CONCAT(object_schema, '.', object_name, ' (', index_name, ')') from sys.schema_unused_indexes where object_schema not in ('performance_schema', 'mysql', 'information_schema', 'sys')"
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- my ( $schema, $table, $index ) = $lQuery =~ /^(.*?)\.(.*?)\s\((.*?)\)$/;
- push(
- @modeling,
- {
- type => 'unused_index',
- schema => $schema,
- table => $table,
- index => $index,
- }
- );
- $nbL++;
- }
- if ( $nbL > 1 ) {
- my $idx_count = $nbL - 1;
- badprint "Performance schema: $idx_count unused index(es) found.";
- push( @generalrec,
-"Unused indexes found: $idx_count index(es) should be reviewed and potentially removed."
- );
- }
- else {
- infoprint "No information found or indicators deactivated.";
- }
-
- # Full table scans
- subheaderprint "Performance schema: Tables with full table scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select * from sys.x\\$schema_tables_with_full_table_scans order by rows_full_scanned DESC'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Latest file IO by latency
- subheaderprint "Performance schema: Latest File IO by latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select thread, file, latency, operation from sys.x\\$latest_file_io ORDER BY latency LIMIT 10;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # FILE by IO read bytes
- subheaderprint "Performance schema: File by IO read bytes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select file, total_read from sys.x\\$io_global_by_file_by_bytes order by total_read DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # FILE by IO written bytes
- subheaderprint "Performance schema: File by IO written bytes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select file, total_written from sys.x\\$io_global_by_file_by_bytes order by total_written DESC LIMIT 15'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # file per IO total latency
- subheaderprint "Performance schema: File per IO total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select file, total_latency from sys.x\\$io_global_by_file_by_latency ORDER BY total_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # file per IO read latency
- subheaderprint "Performance schema: file per IO read latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select file, read_latency from sys.x\\$io_global_by_file_by_latency ORDER BY read_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # file per IO write latency
- subheaderprint "Performance schema: file per IO write latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select file, write_latency from sys.x\\$io_global_by_file_by_latency ORDER BY write_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Event Wait by read bytes
- subheaderprint "Performance schema: Event Wait by read bytes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select event_name, total_read from sys.x\\$io_global_by_wait_by_bytes order by total_read DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Event Wait by write bytes
- subheaderprint "Performance schema: Event Wait written bytes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select event_name, total_written from sys.x\\$io_global_by_wait_by_bytes order by total_written DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # event per wait total latency
- subheaderprint "Performance schema: event per wait total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select event_name, total_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY total_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # event per wait read latency
- subheaderprint "Performance schema: event per wait read latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select event_name, read_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY read_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # event per wait write latency
- subheaderprint "Performance schema: event per wait write latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select event_name, write_latency from sys.x\\$io_global_by_wait_by_latency ORDER BY write_latency DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- #schema_index_statistics
- # TOP 15 most read index
- subheaderprint "Performance schema: Top 15 most read indexes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, rows_selected from sys.x\\$schema_index_statistics ORDER BY ROWs_selected DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 most used index
- subheaderprint "Performance schema: Top 15 most modified indexes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_index_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high read latency index
- subheaderprint "Performance schema: Top 15 high read latency index";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, select_latency from sys.x\\$schema_index_statistics ORDER BY select_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high insert latency index
- subheaderprint "Performance schema: Top 15 most modified indexes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, insert_latency from sys.x\\$schema_index_statistics ORDER BY insert_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high update latency index
- subheaderprint "Performance schema: Top 15 high update latency index";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, update_latency from sys.x\\$schema_index_statistics ORDER BY update_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high delete latency index
- subheaderprint "Performance schema: Top 15 high delete latency index";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name,index_name, delete_latency from sys.x\\$schema_index_statistics ORDER BY delete_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 most read tables
- subheaderprint "Performance schema: Top 15 most read tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, rows_fetched from sys.x\\$schema_table_statistics ORDER BY ROWs_fetched DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 most used tables
- subheaderprint "Performance schema: Top 15 most modified tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, rows_inserted+rows_updated+rows_deleted AS changes from sys.x\\$schema_table_statistics ORDER BY rows_inserted+rows_updated+rows_deleted DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high read latency tables
- subheaderprint "Performance schema: Top 15 high read latency tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, fetch_latency from sys.x\\$schema_table_statistics ORDER BY fetch_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high insert latency tables
- subheaderprint "Performance schema: Top 15 high insert latency tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, insert_latency from sys.x\\$schema_table_statistics ORDER BY insert_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high update latency tables
- subheaderprint "Performance schema: Top 15 high update latency tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, update_latency from sys.x\\$schema_table_statistics ORDER BY update_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # TOP 15 high delete latency tables
- subheaderprint "Performance schema: Top 15 high delete latency tables";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select table_schema, table_name, delete_latency from sys.x\\$schema_table_statistics ORDER BY delete_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- # Redundant indexes
- subheaderprint "Performance schema: Redundant indexes";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select CONCAT(table_schema, ".", table_name, " (", redundant_index_name, ") redundant of ", dominant_index_name, " - SQL: ", sql_drop_index) from sys.schema_redundant_indexes;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- my ( $schema, $table, $redundant, $dominant, $sql ) =
- $lQuery =~
- /^(.*?)\.(.*?)\s\((.*?)\)\sredundant\sof\s(.*?)\s-\sSQL:\s(.*)$/;
- push(
- @modeling,
- {
- type => 'redundant_index',
- schema => $schema,
- table => $table,
- index => $redundant,
- dominant_index => $dominant,
- sql => $sql,
- }
- );
- $nbL++;
- }
- if ( $nbL > 1 ) {
- my $idx_count = $nbL - 1;
- badprint "Performance schema: $idx_count redundant index(es) found.";
- push( @generalrec,
-"Redundant indexes found: $idx_count index(es) should be reviewed and potentially removed."
- );
- }
- else {
- infoprint "No information found or indicators deactivated.";
- }
-
- subheaderprint "Performance schema: Table not using InnoDB buffer";
- $nbL = 1;
- for my $lQuery (
- select_array(
-' Select table_schema, table_name from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NULL;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 Tables using InnoDB buffer";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select table_schema,table_name,innodb_buffer_allocated from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_allocated DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 Tables with InnoDB buffer free";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select table_schema,table_name,innodb_buffer_free from sys.x\\$schema_table_statistics_with_buffer where innodb_buffer_allocated IS NOT NULL ORDER BY innodb_buffer_free DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 Most executed queries";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), exec_count from sys.x\\$statement_analysis order by exec_count DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Latest SQL queries in errors or warnings";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select LEFT(query, 120), last_seen from sys.x\\$statements_with_errors_or_warnings ORDER BY last_seen LIMIT 40;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 20 queries with full table scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_full_table_scans order BY exec_count DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Last 50 queries with full table scans";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_full_table_scans order BY last_seen DESC LIMIT 50;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 reader queries (95% percentile)";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), rows_sent from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY ROWs_sent DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 most row look queries (95% percentile)";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), rows_examined AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY rows_examined DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 total latency queries (95% percentile)";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY total_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 max latency queries (95% percentile)";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), max_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY max_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 average latency queries (95% percentile)";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), avg_latency AS search from sys.x\\$statements_with_runtimes_in_95th_percentile ORDER BY avg_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 20 queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_sorting order BY exec_count DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Last 50 queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_sorting order BY last_seen DESC LIMIT 50;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 row sorting queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), rows_sorted from sys.x\\$statements_with_sorting ORDER BY ROWs_sorted DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 total latency queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_sorting ORDER BY total_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 merge queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), sort_merge_passes AS search from sys.x\\$statements_with_sorting ORDER BY sort_merge_passes DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 average sort merges queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), avg_sort_merges AS search from sys.x\\$statements_with_sorting ORDER BY avg_sort_merges DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 scans queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), sorts_using_scans AS search from sys.x\\$statements_with_sorting ORDER BY sorts_using_scans DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 range queries with sort";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), sort_using_range AS search from sys.x\\$statements_with_sorting ORDER BY sort_using_range DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
-##################################################################################
-
- #statements_with_temp_tables
-
-#mysql> desc statements_with_temp_tables;
-#+--------------------------+---------------------+------+-----+---------------------+-------+
-#| Field | Type | Null | Key | Default | Extra |
-#+--------------------------+---------------------+------+-----+---------------------+-------+
-#| query | longtext | YES | | NULL | |
-#| db | varchar(64) | YES | | NULL | |
-#| exec_count | bigint(20) unsigned | NO | | NULL | |
-#| total_latency | text | YES | | NULL | |
-#| memory_tmp_tables | bigint(20) unsigned | NO | | NULL | |
-#| disk_tmp_tables | bigint(20) unsigned | NO | | NULL | |
-#| avg_tmp_tables_per_query | decimal(21,0) | NO | | 0 | |
-#| tmp_tables_to_disk_pct | decimal(24,0) | NO | | 0 | |
-#| first_seen | timestamp | NO | | 0000-00-00 00:00:00 | |
-#| last_seen | timestamp | NO | | 0000-00-00 00:00:00 | |
-#| digest | varchar(32) | YES | | NULL | |
-#+--------------------------+---------------------+------+-----+---------------------+-------+
-#11 rows in set (0,01 sec)#
-#
- subheaderprint "Performance schema: Top 20 queries with temp table";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), exec_count from sys.x\\$statements_with_temp_tables order BY exec_count DESC LIMIT 20;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Last 50 queries with temp table";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), last_seen from sys.x\\$statements_with_temp_tables order BY last_seen DESC LIMIT 50;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint
- "Performance schema: Top 15 total latency queries with temp table";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select db, LEFT(query, 120), total_latency AS search from sys.x\\$statements_with_temp_tables ORDER BY total_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 queries with temp table to disk";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select db, LEFT(query, 120), disk_tmp_tables from sys.x\\$statements_with_temp_tables ORDER BY disk_tmp_tables DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
-##################################################################################
- #wait_classes_global_by_latency
-
-#mysql> select * from wait_classes_global_by_latency;
-#-----------------+-------+---------------+-------------+-------------+-------------+
-# event_class | total | total_latency | min_latency | avg_latency | max_latency |
-#-----------------+-------+---------------+-------------+-------------+-------------+
-# wait/io/file | 15381 | 1.23 s | 0 ps | 80.12 us | 230.64 ms |
-# wait/io/table | 59 | 7.57 ms | 5.45 us | 128.24 us | 3.95 ms |
-# wait/lock/table | 69 | 3.22 ms | 658.84 ns | 46.64 us | 1.10 ms |
-#-----------------+-------+---------------+-------------+-------------+-------------+
-# rows in set (0,00 sec)
-
- subheaderprint "Performance schema: Top 15 class events by number";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select event_class, total from sys.x\\$wait_classes_global_by_latency ORDER BY total DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 30 events by number";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select events, total from sys.x\\$waits_global_by_latency ORDER BY total DESC LIMIT 30;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 class events by total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select event_class, total_latency from sys.x\\$wait_classes_global_by_latency ORDER BY total_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 30 events by total latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'use sys;select events, total_latency from sys.x\\$waits_global_by_latency ORDER BY total_latency DESC LIMIT 30;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 15 class events by max latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select event_class, max_latency from sys.x\\$wait_classes_global_by_latency ORDER BY max_latency DESC LIMIT 15;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
- subheaderprint "Performance schema: Top 30 events by max latency";
- $nbL = 1;
- for my $lQuery (
- select_array(
-'select events, max_latency from sys.x\\$waits_global_by_latency ORDER BY max_latency DESC LIMIT 30;'
- )
- )
- {
- infoprint " +-- $nbL: $lQuery";
- $nbL++;
- }
- infoprint "No information found or indicators deactivated."
- if ( $nbL == 1 );
-
-}
-
-# Recommendations for Aria Engine
-sub mariadb_aria {
- subheaderprint "Aria Metrics";
-
- # Aria
- if ( !defined $myvar{'have_aria'} ) {
- infoprint "Aria Storage Engine not available.";
- return;
- }
- if ( $myvar{'have_aria'} ne "YES" ) {
- infoprint "Aria Storage Engine is disabled.";
- return;
- }
- infoprint "Aria Storage Engine is enabled.";
-
- # Aria pagecache
- if ( !defined( $mycalc{'total_aria_indexes'} ) ) {
- push( @generalrec,
- "Unable to calculate Aria index size on MySQL server" );
- }
- else {
- if (
- $myvar{'aria_pagecache_buffer_size'} < $mycalc{'total_aria_indexes'}
- && $mycalc{'pct_aria_keys_from_mem'} < 95 )
- {
- badprint "Aria pagecache size / total Aria indexes: "
- . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/"
- . hr_bytes( $mycalc{'total_aria_indexes'} ) . "";
- push( @adjvars,
- "aria_pagecache_buffer_size (> "
- . hr_bytes( $mycalc{'total_aria_indexes'} )
- . ")" );
- }
- else {
- goodprint "Aria pagecache size / total Aria indexes: "
- . hr_bytes( $myvar{'aria_pagecache_buffer_size'} ) . "/"
- . hr_bytes( $mycalc{'total_aria_indexes'} ) . "";
- }
- if ( $mystat{'Aria_pagecache_read_requests'} > 0 ) {
- if ( $mycalc{'pct_aria_keys_from_mem'} < 95 ) {
- badprint
-"Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% ("
- . hr_num( $mystat{'Aria_pagecache_read_requests'} )
- . " cached / "
- . hr_num( $mystat{'Aria_pagecache_reads'} )
- . " reads)";
- }
- else {
- goodprint
-"Aria pagecache hit rate: $mycalc{'pct_aria_keys_from_mem'}% ("
- . hr_num( $mystat{'Aria_pagecache_read_requests'} )
- . " cached / "
- . hr_num( $mystat{'Aria_pagecache_reads'} )
- . " reads)";
- }
- }
- else {
-
- # No queries have run that would use keys
- }
- }
-}
-
-# Recommendations for TokuDB
-sub mariadb_tokudb {
- subheaderprint "TokuDB Metrics";
-
- # AriaDB
- unless ( defined $myvar{'have_tokudb'}
- && $myvar{'have_tokudb'} eq "YES" )
- {
- infoprint "TokuDB is disabled.";
- return;
- }
- infoprint "TokuDB is enabled.";
-
- # Not implemented
-}
-
-# Recommendations for XtraDB
-sub mariadb_xtradb {
- subheaderprint "XtraDB Metrics";
-
- # XtraDB
- unless ( defined $myvar{'have_xtradb'}
- && $myvar{'have_xtradb'} eq "YES" )
- {
- infoprint "XtraDB is disabled.";
- return;
- }
- infoprint "XtraDB is enabled.";
- infoprint "Note that MariaDB 10.2 makes use of InnoDB, not XtraDB."
-
- # Not implemented
-}
-
-# Recommendations for RocksDB
-sub mariadb_rockdb {
- subheaderprint "RocksDB Metrics";
-
- # RocksDB
- unless ( defined $myvar{'have_rocksdb'}
- && $myvar{'have_rocksdb'} eq "YES" )
- {
- infoprint "RocksDB is disabled.";
- return;
- }
- infoprint "RocksDB is enabled.";
-
- # Not implemented
-}
-
-# Recommendations for Spider
-sub mariadb_spider {
- subheaderprint "Spider Metrics";
-
- # Spider
- unless ( defined $myvar{'have_spider'}
- && $myvar{'have_spider'} eq "YES" )
- {
- infoprint "Spider is disabled.";
- return;
- }
- infoprint "Spider is enabled.";
-
- # Not implemented
-}
-
-# Recommendations for Connect
-sub mariadb_connect {
- subheaderprint "Connect Metrics";
-
- # Connect
- unless ( defined $myvar{'have_connect'}
- && $myvar{'have_connect'} eq "YES" )
- {
- infoprint "Connect is disabled.";
- return;
- }
- infoprint "Connect is enabled.";
-
- # Not implemented
-}
-
-# Perl trim function to remove whitespace from the start and end of the string
-sub trim {
- my $string = shift;
- return "" unless defined($string);
- $string =~ s/^\s+//;
- $string =~ s/\s+$//;
- return $string;
-}
-
-sub get_wsrep_options {
- return () unless defined $myvar{'wsrep_provider_options'};
-
- my @galera_options = split /;/, $myvar{'wsrep_provider_options'};
- @galera_options = remove_cr @galera_options;
- @galera_options = remove_empty @galera_options;
-
- #debugprint Dumper( \@galera_options ) if $opt{debug};
- return @galera_options;
-}
-
-sub get_gcache_memory {
- my $gCacheMem = hr_raw( get_wsrep_option('gcache.size') );
-
- return 0 unless defined $gCacheMem and $gCacheMem ne '';
- return $gCacheMem;
-}
-
-sub get_wsrep_option {
- my $key = shift;
- return '' unless defined $myvar{'wsrep_provider_options'};
- my @galera_options = get_wsrep_options;
- return '' unless scalar(@galera_options) > 0;
- my @memValues = grep /\s*$key =/, @galera_options;
- my $memValue = $memValues[0];
- return 0 unless defined $memValue;
- $memValue =~ s/.*=\s*(.+)$/$1/g;
- return $memValue;
-}
-
-# REcommendations for Tables
-sub mysql_table_structures {
- return 0 unless ( $opt{structstat} > 0 );
- subheaderprint "Table structures analysis";
-
- my @primaryKeysNbTables = select_array(
- "Select CONCAT(c.table_schema, ',' , c.table_name)
-from information_schema.columns c
-join information_schema.tables t using (TABLE_SCHEMA, TABLE_NAME)
-where c.table_schema not in ('sys', 'mysql', 'information_schema', 'performance_schema')
- and t.table_type = 'BASE TABLE'
-group by c.table_schema,c.table_name
-having sum(if(c.column_key in ('PRI', 'UNI'), 1, 0)) = 0"
- );
-
- my $tmpContent = 'Schema,Table';
- if ( scalar(@primaryKeysNbTables) > 0 ) {
- badprint "Following table(s) don't have primary key:";
- foreach my $badtable (@primaryKeysNbTables) {
- badprint "\t$badtable";
- push @{ $result{'Tables without PK'} }, $badtable;
- $tmpContent .= "\n$badtable";
- }
- push @generalrec,
-"Ensure that all table(s) get an explicit primary keys for performance, maintenance and also for replication";
- push @modeling, "Following table(s) don't have primary key: "
- . join( ', ', @primaryKeysNbTables );
-
- }
- else {
- goodprint "All tables get a primary key";
- }
- dump_into_file( "tables_without_primary_keys.csv", $tmpContent );
-
- # Advanced PK checks
- my @pkInfo = select_array(
-"SELECT c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_TYPE
-FROM information_schema.columns c
-JOIN information_schema.tables t USING (TABLE_SCHEMA, TABLE_NAME)
-WHERE t.TABLE_TYPE = 'BASE TABLE'
- AND c.COLUMN_KEY = 'PRI'
- AND c.TABLE_SCHEMA NOT IN ('sys', 'mysql', 'information_schema', 'performance_schema')"
- );
-
- foreach my $pk (@pkInfo) {
- my ( $schema, $table, $column, $datatype, $columntype ) = split /\t/,
- $pk;
- $schema //= '';
- $table //= '';
- $column //= '';
- $datatype //= '';
- $columntype //= '';
-
- # PK Naming Convention
- if ( $column ne 'id' && $column ne "${table}_id" ) {
- badprint
-"Table $schema.$table: Primary key '$column' does not follow 'id' or '${table}_id' naming convention";
- push @generalrec,
-"Use 'id' or '${table}_id' for Primary Key naming in $schema.$table";
- push @modeling,
-"Table $schema.$table: Primary key '$column' does not follow naming convention (id or ${table}_id)";
- }
-
- # Surrogate Key Recommendation
- if ( $datatype !~ /int/i
- || $columntype !~ /unsigned/i
- || $columntype !~ /auto_increment/i )
- {
- # Check if it might be a UUID
- if ( $column =~ /uuid/i ) {
- if ( $datatype !~ /binary/i || $columntype !~ /16/ ) {
- badprint
-"Table $schema.$table: UUID primary key '$column' is not optimized (use BINARY(16))";
- push @generalrec,
-"Use optimized BINARY(16) for UUID Primary Keys in $schema.$table";
- push @modeling,
-"Table $schema.$table: UUID primary key '$column' is not optimized (use BINARY(16))";
- }
- }
- else {
- badprint
-"Table $schema.$table: Primary key '$column' is not a recommended surrogate key (BIGINT UNSIGNED AUTO_INCREMENT)";
- push @generalrec,
-"Use BIGINT UNSIGNED AUTO_INCREMENT for Primary Keys in $schema.$table";
- push @modeling,
-"Table $schema.$table: Primary key '$column' is not a recommended surrogate key (BIGINT UNSIGNED AUTO_INCREMENT)";
- }
- }
- }
-
- # Large Tables (>1GB) without Secondary Indexes
- my @largeTablesWithoutIndexes = select_array(
- "SELECT TABLE_SCHEMA, TABLE_NAME, (DATA_LENGTH + INDEX_LENGTH)
-FROM information_schema.tables t
-WHERE TABLE_TYPE = 'BASE TABLE'
- AND (DATA_LENGTH + INDEX_LENGTH) > 1024*1024*1024
- AND (SELECT COUNT(*) FROM information_schema.statistics s WHERE s.TABLE_SCHEMA = t.TABLE_SCHEMA AND s.TABLE_NAME = t.TABLE_NAME AND s.INDEX_NAME != 'PRIMARY') = 0
- AND TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- if (@largeTablesWithoutIndexes) {
- foreach my $lt (@largeTablesWithoutIndexes) {
- my ( $schema, $table, $size ) = split /\t/, $lt;
- $schema //= '';
- $table //= '';
- $size //= 0;
- badprint "Table $schema.$table is large ("
- . hr_bytes($size)
- . ") and has no secondary indexes";
- push @generalrec,
-"Add secondary indexes to large table $schema.$table to improve query performance";
- push @modeling,
- "Table $schema.$table is large ("
- . hr_bytes($size)
- . ") and has no secondary indexes";
- }
- }
-
- # Foreign Key Type Mismatches
- my @fkMismatches = select_array(
-"SELECT CONCAT(k.TABLE_SCHEMA, '.', k.TABLE_NAME, ' (', k.COLUMN_NAME, ': ', c1.COLUMN_TYPE, ') -> ', k.REFERENCED_TABLE_SCHEMA, '.', k.REFERENCED_TABLE_NAME, ' (', k.REFERENCED_COLUMN_NAME, ': ', c2.COLUMN_TYPE, ')')
-FROM information_schema.KEY_COLUMN_USAGE k
-JOIN information_schema.COLUMNS c1 ON k.TABLE_SCHEMA = c1.TABLE_SCHEMA AND k.TABLE_NAME = c1.TABLE_NAME AND k.COLUMN_NAME = c1.COLUMN_NAME
-JOIN information_schema.COLUMNS c2 ON k.REFERENCED_TABLE_SCHEMA = c2.TABLE_SCHEMA AND k.REFERENCED_TABLE_NAME = c2.TABLE_NAME AND k.REFERENCED_COLUMN_NAME = c2.COLUMN_NAME
-WHERE k.REFERENCED_TABLE_NAME IS NOT NULL
- AND (c1.DATA_TYPE != c2.DATA_TYPE OR c1.COLUMN_TYPE != c2.COLUMN_TYPE)
- AND k.TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- if (@fkMismatches) {
- badprint "Following Foreign Key(s) have data type mismatches:";
- foreach my $fk (@fkMismatches) {
- badprint "\t$fk";
- push @generalrec, "Fix data type mismatch for Foreign Key: $fk";
- push @modeling, "Foreign Key type mismatch: $fk";
- }
- }
-
- my @nonInnoDBTables = select_array(
- "select table_schema, table_name, ENGINE
-FROM information_schema.tables t
-WHERE ENGINE <> 'InnoDB'
-and t.table_type = 'BASE TABLE'
-and table_schema not in
-('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- $tmpContent = 'Schema,Table,Engine';
- if ( scalar(@nonInnoDBTables) > 0 ) {
- badprint "Following table(s) are not InnoDB table:";
- push @generalrec,
-"Ensure that all table(s) are InnoDB tables for performance and also for replication";
- push @modeling, "Following table(s) are not InnoDB: "
- . join( ', ', @nonInnoDBTables );
- foreach my $badtable (@nonInnoDBTables) {
- if ( $badtable =~ /Memory/i ) {
- badprint
-"Table $badtable is a MEMORY table. It's suggested to use only InnoDB tables in production";
- }
- else {
- badprint "\t$badtable";
- }
- $tmpContent .= "\n$badtable";
- }
- }
- else {
- goodprint "All tables are InnoDB tables";
- }
- dump_into_file( "tables_non_innodb.csv", $tmpContent );
-
- my @nonutf8columns = select_array(
-"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH)
-from information_schema.columns
-WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema')
-and (CHARacter_set_name NOT LIKE 'utf8%'
-or COLLATION_name NOT LIKE 'utf8%');"
- );
- $tmpContent =
- 'Schema,Table,Column, Charset, Collation, Data Type, Max Length';
- if ( scalar(@nonutf8columns) > 0 ) {
- badprint "Following character columns(s) are not utf8 compliant:";
- push @generalrec,
-"Ensure that all text colums(s) are UTF-8 compliant for encoding support and performance";
- push @modeling, "Following collection(s) are not UTF-8 compliant: "
- . join( ', ', @nonutf8columns );
- foreach my $badtable (@nonutf8columns) {
- badprint "\t$badtable";
- $tmpContent .= "\n$badtable";
- }
- }
- else {
- goodprint "All columns are UTF-8 compliant";
- }
- dump_into_file( "columns_non_utf8.csv", $tmpContent );
-
- my @utf8columns = select_array(
-"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', CHARacter_set_name, ',', COLLATION_name, ',', data_type, ',', CHARACTER_MAXIMUM_LENGTH)
-from information_schema.columns
-WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema')
-and (CHARacter_set_name LIKE 'utf8%'
-or COLLATION_name LIKE 'utf8%');"
- );
- $tmpContent =
- 'Schema,Table,Column, Charset, Collation, Data Type, Max Length';
- foreach my $badtable (@utf8columns) {
- $tmpContent .= "\n$badtable";
- }
- dump_into_file( "columns_utf8.csv", $tmpContent );
-
- my @ftcolumns = select_array(
-"SELECT CONCAT(table_schema, ',', table_name, ',', column_name, ',', data_type)
-from information_schema.columns
-WHERE table_schema not in ('sys', 'mysql', 'performance_schema', 'information_schema')
-AND data_type='FULLTEXT';"
- );
- $tmpContent = 'Schema,Table,Column, Data Type';
- foreach my $ctable (@ftcolumns) {
- $tmpContent .= "\n$ctable";
- }
- dump_into_file( "fulltext_columns.csv", $tmpContent );
-
- mysql_naming_conventions();
- mysql_foreign_key_checks();
- mysql_80_modeling_checks();
- mysql_datatype_optimization();
- mysql_schema_sanitization();
-}
-
-sub mysql_80_modeling_checks {
- return unless mysql_version_ge( 8, 0 );
-
- my $is_mariadb = (
- ( $myvar{'version'} =~ /MariaDB/i )
- or ( $myvar{'version_comment'} =~ /MariaDB/i )
- );
- my $header =
- $is_mariadb
- ? "MariaDB 10.x+ Specific Modeling"
- : "MySQL 8.0+ Specific Modeling";
- subheaderprint $header;
-
- my $modeling80Count = 0;
-
- # JSON indexability
- my @jsonColumns = select_array(
-"SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM information_schema.columns WHERE DATA_TYPE = 'json' AND TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $jc (@jsonColumns) {
- my ( $schema, $table, $column ) = split /\t/, $jc;
- $schema //= '';
- $table //= '';
- $column //= '';
-
- # Check if there are generated columns for this table
- my @genCols = select_array(
-"SELECT COLUMN_NAME FROM information_schema.columns WHERE TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table' AND EXTRA LIKE '%VIRTUAL%'"
- );
- if ( scalar(@genCols) == 0 ) {
- infoprint
-"Table $schema.$table: JSON column '$column' detected without Virtual Generated Columns for indexing";
- push @generalrec,
-"Consider using Generated Columns to index frequently searched attributes in JSON column $schema.$table.$column";
- push @modeling,
-"Table $schema.$table: JSON column '$column' detected without Virtual Generated Columns for indexing";
- $modeling80Count++;
- }
- }
-
- # Invisible Indexes (MySQL: IS_VISIBLE='NO', MariaDB: IGNORED='YES')
- my $visible_col = $is_mariadb ? "IGNORED" : "IS_VISIBLE";
- my $visible_val = $is_mariadb ? "'YES'" : "'NO'";
-
- my @invisibleIdx = select_array(
-"SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME FROM information_schema.statistics WHERE $visible_col = $visible_val AND TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $ii (@invisibleIdx) {
- my ( $schema, $table, $index ) = split /\t/, $ii;
- $schema //= '';
- $table //= '';
- $index //= '';
- infoprint "Index $schema.$table.$index is INVISIBLE";
- push @modeling, "Index $schema.$table.$index is INVISIBLE";
- $modeling80Count++;
- }
-
- # Check Constraints
- if ( mysql_version_ge( 8, 0, 16 ) ) {
- my @checkConstraints = select_array(
-"SELECT CONSTRAINT_SCHEMA, TABLE_NAME, CONSTRAINT_NAME FROM information_schema.table_constraints WHERE CONSTRAINT_TYPE = 'CHECK' AND CONSTRAINT_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- if ( scalar(@checkConstraints) == 0 ) {
-
-# Maybe too noisy to always suggest, but it's a good practice
-# infoprint "No CHECK constraints detected; consider using them for data integrity";
- }
- else {
- # We skip counting these as we don't badprint/infoprint them for now
- }
- }
- goodprint "No MySQL 8.0+ specific modeling issues found"
- if $modeling80Count == 0;
-}
-
-sub mysql_datatype_optimization {
- subheaderprint "Data Type optimization";
-
- # NULLability
- my @nullableCols = select_array(
-"SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM information_schema.columns WHERE IS_NULLABLE = 'YES' AND TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- my $nullableCount = scalar(@nullableCols);
- if ( $nullableCount > 20 ) {
- infoprint
-"There are $nullableCount columns with NULL enabled. Consider using NOT NULL where possible for better performance.";
- push @modeling,
-"There are $nullableCount columns with NULL enabled. Consider using NOT NULL where possible for better performance.";
- }
- else {
- goodprint "No data type optimization recommendations";
- }
-
- # BIGINT vs INT
- # This is a bit hard to check without looking at table rows and max values
-}
-
-sub mysql_naming_conventions {
- subheaderprint "Naming conventions analysis";
-
- my $namingIssues = 0;
-
- # Table Naming
- my @tables = select_array(
-"SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.tables WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $t (@tables) {
- my ( $schema, $table ) = split /\t/, $t;
- $schema //= '';
- $table //= '';
-
- # Plural check (very basic: ends with 's' but not 'ss')
- if ( ( $table // '' ) =~ /[^s]s$/i
- && ( $table // '' ) !~ /status|address|glass|process/i )
- {
- badprint
- "Table $schema.$table: Plural name detected (prefer singular)";
- push @generalrec, "Use singular names for table $schema.$table";
- push @modeling,
- "Table $schema.$table: Plural name detected (prefer singular)";
- $namingIssues++;
- }
-
- # Casing check (detect CamelCase/PascalCase)
- if ( ( $table // '' ) =~ /[a-z][A-Z]/ ) {
- badprint "Table $schema.$table: Non-snake_case name detected";
- push @generalrec, "Use snake_case for table $schema.$table";
- push @modeling,
- "Table $schema.$table: Non-snake_case name detected";
- $namingIssues++;
- }
- }
-
- # Column Naming
- my @columns = select_array(
-"SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM information_schema.columns WHERE TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $c (@columns) {
- my ( $schema, $table, $column, $datatype ) = split /\t/, $c;
- $schema //= '';
- $table //= '';
- $column //= '';
- $datatype //= '';
-
- # Casing check
- if ( ( $column // '' ) =~ /[a-z][A-Z]/ ) {
- badprint
- "Column $schema.$table.$column: Non-snake_case name detected";
- push @generalrec,
- "Use snake_case for column $schema.$table.$column";
- push @modeling,
- "Column $schema.$table.$column: Non-snake_case name detected";
- $namingIssues++;
- }
-
- # Boolean naming
- if ( ( $datatype // '' ) =~ /tinyint\(1\)|bool/i ) {
- if ( ( $column // '' ) !~ /^(is_|has_|was_|had_)/ ) {
- infoprint
-"Column $schema.$table.$column: Boolean-like column missing verbal prefix (is_, has_, etc.)";
- push @modeling,
-"Column $schema.$table.$column: Boolean-like column missing verbal prefix (is_, has_, etc.)";
-
- # Not a badprint as it's a recommendation
- $namingIssues++;
- }
- }
-
- # Date naming
- if ( ( $datatype // '' ) =~ /date|time/i ) {
- if ( ( $column // '' ) !~ /(_at|_date|_time)$/ ) {
- infoprint
-"Column $schema.$table.$column: Date/Time column missing explicit suffix (_at, _date, _time)";
- push @modeling,
-"Column $schema.$table.$column: Date/Time column missing explicit suffix (_at, _date, _time)";
- $namingIssues++;
- }
- }
- }
- goodprint "No naming convention issues found" if $namingIssues == 0;
-}
-
-sub mysql_foreign_key_checks {
- subheaderprint "Foreign Key analysis";
-
- my $fkIssues = 0;
-
- # Unconstrained _id columns
- my @unconstrainedId = select_array(
- "SELECT c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME
-FROM information_schema.columns c
-LEFT JOIN information_schema.key_column_usage k ON c.TABLE_SCHEMA = k.TABLE_SCHEMA AND c.TABLE_NAME = k.TABLE_NAME AND c.COLUMN_NAME = k.COLUMN_NAME AND k.REFERENCED_TABLE_NAME IS NOT NULL
-WHERE c.COLUMN_NAME LIKE '%_id'
- AND k.COLUMN_NAME IS NULL
- AND c.TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $id (@unconstrainedId) {
- my ( $schema, $table, $column ) = split /\t/, $id;
- $schema //= '';
- $table //= '';
- $column //= '';
-
- # Exclude PKs that are named table_id
- next if $column eq "${table}_id";
-
- badprint
-"Column $schema.$table.$column ends in '_id' but has no FOREIGN KEY constraint";
- push @generalrec,
- "Add FOREIGN KEY constraint to $schema.$table.$column";
- push @modeling,
-"Column $schema.$table.$column ends in '_id' but has no FOREIGN KEY constraint";
- $fkIssues++;
- }
-
- # FK Actions
- my @fkActions = select_array(
-"SELECT rc.CONSTRAINT_SCHEMA, rc.TABLE_NAME, k.COLUMN_NAME, rc.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME, rc.DELETE_RULE
-FROM information_schema.referential_constraints rc
-JOIN information_schema.key_column_usage k ON rc.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME = k.CONSTRAINT_NAME
-WHERE rc.CONSTRAINT_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $fk (@fkActions) {
- my ( $schema, $table, $column, $ref_table, $ref_column, $delete_rule )
- = split /\t/, $fk;
- $schema //= '';
- $table //= '';
- $column //= '';
- $ref_table //= '';
- $ref_column //= '';
- $delete_rule //= '';
- if ( $delete_rule eq 'CASCADE' ) {
- infoprint
-"Constraint on $schema.$table.$column uses ON DELETE CASCADE; ensure this is intended.";
- push @modeling,
-"Constraint on $schema.$table.$column uses ON DELETE CASCADE; ensure this is intended.";
- $fkIssues++;
- }
- }
-
- # Foreign Key Type Mismatches
- my @fkTypeMismatches = select_array(
-"SELECT k.CONSTRAINT_SCHEMA, k.TABLE_NAME, k.COLUMN_NAME, c1.COLUMN_TYPE, k.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME, c2.COLUMN_TYPE
-FROM information_schema.key_column_usage k
-JOIN information_schema.columns c1 ON k.TABLE_SCHEMA = c1.TABLE_SCHEMA AND k.TABLE_NAME = c1.TABLE_NAME AND k.COLUMN_NAME = c1.COLUMN_NAME
-JOIN information_schema.columns c2 ON k.REFERENCED_TABLE_SCHEMA = c2.TABLE_SCHEMA AND k.REFERENCED_TABLE_NAME = c2.TABLE_NAME AND k.REFERENCED_COLUMN_NAME = c2.COLUMN_NAME
-WHERE k.REFERENCED_TABLE_NAME IS NOT NULL
- AND c1.COLUMN_TYPE != c2.COLUMN_TYPE
- AND k.CONSTRAINT_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')"
- );
- foreach my $mm (@fkTypeMismatches) {
- my ( $schema, $table, $col, $type1, $ref_table, $ref_col, $type2 ) =
- split /\t/, $mm;
- $schema //= '';
- $table //= '';
- $col //= '';
- $type1 //= '';
- $ref_table //= '';
- $ref_col //= '';
- $type2 //= '';
- badprint
-"FK Type Mismatch: $schema.$table.$col ($type1) -> $ref_table.$ref_col ($type2)";
- push @generalrec,
-"Fix data type mismatch in Foreign Key $schema.$table.$col ($type1 vs $type2)";
- push @modeling,
-"FK Type Mismatch: $schema.$table.$col ($type1) references $ref_table.$ref_col ($type2)";
- $fkIssues++;
- }
- goodprint "No foreign key issues found" if $fkIssues == 0;
-}
-
-sub mysql_schema_sanitization {
- subheaderprint "Schema sanitization";
-
- my @emptyOrViewOnlySchemas = select_array(
- "SELECT TABLE_SCHEMA,
- SUM(CASE WHEN TABLE_TYPE = 'BASE TABLE' THEN 1 ELSE 0 END),
- SUM(CASE WHEN TABLE_TYPE = 'VIEW' THEN 1 ELSE 0 END)
-FROM information_schema.tables
-WHERE TABLE_SCHEMA NOT IN ('sys', 'mysql', 'performance_schema', 'information_schema')
-GROUP BY TABLE_SCHEMA
-HAVING SUM(CASE WHEN TABLE_TYPE = 'BASE TABLE' THEN 1 ELSE 0 END) = 0"
- );
-
- if ( scalar(@emptyOrViewOnlySchemas) == 0 ) {
- goodprint "No empty or view-only schemas detected";
- }
- else {
- foreach my $s (@emptyOrViewOnlySchemas) {
- my ( $schema, $tables, $views ) = split /\t/, $s;
- $schema //= '';
- $tables //= 0;
- $views //= 0;
- if ( $tables == 0 && $views == 0 ) {
- infoprint "Schema $schema is empty (no tables or views)";
- push @modeling, "Schema $schema is empty (no tables or views)";
- }
- elsif ( $tables == 0 && $views > 0 ) {
- infoprint "Schema $schema contains only views ($views views)";
- push @modeling,
- "Schema $schema contains only views ($views views)";
- }
- }
- }
-}
-
-# Recommendations for Galera
-sub mariadb_galera {
- subheaderprint "Galera Metrics";
-
- # Galera Cluster
- unless ( defined $myvar{'have_galera'}
- && $myvar{'have_galera'} eq "YES" )
- {
- infoprint "Galera is disabled.";
- return;
- }
- infoprint "Galera is enabled.";
- debugprint "Galera variables:";
- foreach my $gvar ( keys %myvar ) {
- next unless $gvar =~ /^wsrep.*/;
- next if $gvar eq 'wsrep_provider_options';
- debugprint "\t" . trim($gvar) . " = " . $myvar{$gvar};
- $result{'Galera'}{'variables'}{$gvar} = $myvar{$gvar};
- }
- if ( not defined( $myvar{'wsrep_on'} ) or $myvar{'wsrep_on'} ne "ON" ) {
- infoprint "Galera is disabled.";
- return;
- }
- debugprint "Galera wsrep provider Options:";
- my @galera_options = get_wsrep_options;
- $result{'Galera'}{'wsrep options'} = get_wsrep_options();
- foreach my $gparam (@galera_options) {
- debugprint "\t" . trim($gparam);
- }
- debugprint "Galera status:";
- foreach my $gstatus ( keys %mystat ) {
- next unless $gstatus =~ /^wsrep.*/;
- debugprint "\t" . trim($gstatus) . " = " . $mystat{$gstatus};
- $result{'Galera'}{'status'}{$gstatus} = $myvar{$gstatus};
- }
- infoprint "GCache is using "
- . hr_bytes_rnd( get_wsrep_option('gcache.mem_size') );
-
- infoprint "CPU cores detected : " . (cpu_cores);
- my $wsrep_threads_var_name = 'wsrep_slave_threads';
- if ( defined( $myvar{'wsrep_applier_threads'} ) ) {
- $wsrep_threads_var_name = 'wsrep_applier_threads';
- }
-
- # Use 1 as a fallback if $myvar{$wsrep_threads_var_name} is undefined or zero,
- # to ensure there is at least one thread for Galera replication.
- my $wsrep_threads_value = $myvar{$wsrep_threads_var_name} || 1;
-
- infoprint "$wsrep_threads_var_name: " . $wsrep_threads_value;
-
- if ( $wsrep_threads_value > ( (cpu_cores) * 4 )
- or $wsrep_threads_value < ( (cpu_cores) * 2 ) )
- {
- badprint
-"$wsrep_threads_var_name is not equal to 2, 3 or 4 times the number of CPU(s)";
- push @adjvars, "$wsrep_threads_var_name = " . ( (cpu_cores) * 4 );
- }
- else {
- goodprint
-"$wsrep_threads_var_name is equal to 2, 3 or 4 times the number of CPU(s)";
- }
-
- if ( $wsrep_threads_value > 1 ) {
- infoprint
- "wsrep parallel slave can cause frequent inconsistency crash.";
- push @adjvars,
-"Set $wsrep_threads_var_name to 1 in case of HA_ERR_FOUND_DUPP_KEY crash on slave";
-
- # check options for parallel slave
- if ( get_wsrep_option('wsrep_slave_FK_checks') eq "OFF" ) {
- badprint "wsrep_slave_FK_checks is off with parallel slave";
- push @adjvars,
- "wsrep_slave_FK_checks should be ON when using parallel slave";
- }
-
- # wsrep_slave_UK_checks seems useless in MySQL source code
- if ( $myvar{'innodb_autoinc_lock_mode'} != 2 ) {
- badprint
- "innodb_autoinc_lock_mode is incorrect with parallel slave";
- push @adjvars,
- "innodb_autoinc_lock_mode should be 2 when using parallel slave";
- }
- }
-
- if ( get_wsrep_option('gcs.fc_limit') != $wsrep_threads_value * 5 ) {
- badprint
- "gcs.fc_limit should be equal to 5 * $wsrep_threads_var_name (="
- . ( $wsrep_threads_value * 5 ) . ")";
- push @adjvars, "gcs.fc_limit= $wsrep_threads_var_name * 5 (="
- . ( $wsrep_threads_value * 5 ) . ")";
- }
- else {
- goodprint "gcs.fc_limit is equal to 5 * $wsrep_threads_var_name ( ="
- . get_wsrep_option('gcs.fc_limit') . ")";
- }
-
- if ( get_wsrep_option('gcs.fc_factor') != 0.8 ) {
- badprint "gcs.fc_factor should be equal to 0.8 (="
- . get_wsrep_option('gcs.fc_factor') . ")";
- push @adjvars, "gcs.fc_factor=0.8";
- }
- else {
- goodprint "gcs.fc_factor is equal to 0.8";
- }
- if ( get_wsrep_option('wsrep_flow_control_paused') > 0.02 ) {
- badprint "Fraction of time node pause flow control > 0.02";
- }
- else {
- goodprint
-"Flow control fraction seems to be OK (wsrep_flow_control_paused <= 0.02)";
- }
-
- if ( $myvar{'binlog_format'} ne 'ROW' ) {
- badprint "Binlog format should be in ROW mode.";
- push @adjvars, "binlog_format = ROW";
- }
- else {
- goodprint "Binlog format is in ROW mode.";
- }
- if ( $myvar{'innodb_flush_log_at_trx_commit'} != 0 ) {
- badprint "InnoDB flush log at each commit should be disabled.";
- push @adjvars, "innodb_flush_log_at_trx_commit = 0";
- }
- else {
- goodprint "InnoDB flush log at each commit is disabled for Galera.";
- }
-
- 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" )
- {
- goodprint "Galera WsREP is enabled.";
- if ( defined( $myvar{'wsrep_cluster_address'} )
- and trim("$myvar{'wsrep_cluster_address'}") ne "" )
- {
- goodprint "Galera Cluster address is defined: "
- . $myvar{'wsrep_cluster_address'};
- my @NodesTmp = split /,/, $myvar{'wsrep_cluster_address'};
- my $nbNodes = @NodesTmp;
- infoprint "There are $nbNodes nodes in wsrep_cluster_address";
- my $nbNodesSize = trim( $mystat{'wsrep_cluster_size'} );
- if ( $nbNodesSize == 3 or $nbNodesSize == 5 ) {
- goodprint "There are $nbNodesSize nodes in wsrep_cluster_size.";
- }
- else {
- badprint
-"There are $nbNodesSize nodes in wsrep_cluster_size. Prefer 3 or 5 nodes architecture.";
- push @generalrec, "Prefer 3 or 5 nodes architecture.";
- }
-
- # wsrep_cluster_address doesn't include garbd nodes
- if ( $nbNodes > $nbNodesSize ) {
- badprint
-"All cluster nodes are not detected. wsrep_cluster_size less than node count in wsrep_cluster_address";
- }
- else {
- goodprint "All cluster nodes detected.";
- }
- }
- else {
- badprint "Galera Cluster address is undefined";
- push @adjvars,
- "set up wsrep_cluster_address variable for Galera replication";
- }
- if ( defined( $myvar{'wsrep_cluster_name'} )
- and trim( $myvar{'wsrep_cluster_name'} ) ne "" )
- {
- goodprint "Galera Cluster name is defined: "
- . $myvar{'wsrep_cluster_name'};
- }
- else {
- badprint "Galera Cluster name is undefined";
- push @adjvars,
- "set up wsrep_cluster_name variable for Galera replication";
- }
- if ( defined( $myvar{'wsrep_node_name'} )
- and trim( $myvar{'wsrep_node_name'} ) ne "" )
- {
- goodprint "Galera Node name is defined: "
- . $myvar{'wsrep_node_name'};
- }
- else {
- badprint "Galera node name is undefined";
- push @adjvars,
- "set up wsrep_node_name variable for Galera replication";
- }
- if ( trim( $myvar{'wsrep_notify_cmd'} ) ne "" ) {
- goodprint "Galera Notify command is defined.";
- }
- else {
- badprint "Galera Notify command is not defined.";
- push( @adjvars,
- "set up parameter wsrep_notify_cmd to be notified" );
- }
- if ( trim( $myvar{'wsrep_sst_method'} ) !~ "^xtrabackup.*"
- and trim( $myvar{'wsrep_sst_method'} ) !~ "^mariabackup" )
- {
- badprint "Galera SST method is not xtrabackup based.";
- push( @adjvars,
-"set up parameter wsrep_sst_method to xtrabackup based parameter"
- );
- }
- else {
- goodprint "SST Method is based on xtrabackup.";
- }
- if (
- (
- defined( $myvar{'wsrep_OSU_method'} )
- && trim( $myvar{'wsrep_OSU_method'} ) eq "TOI"
- )
- || ( defined( $myvar{'wsrep_osu_method'} )
- && trim( $myvar{'wsrep_osu_method'} ) eq "TOI" )
- )
- {
- goodprint "TOI is default mode for upgrade.";
- }
- else {
- badprint "Schema upgrade are not replicated automatically";
- push( @adjvars, "set up parameter wsrep_OSU_method to TOI" );
- }
- infoprint "Max WsRep message : "
- . hr_bytes( $myvar{'wsrep_max_ws_size'} );
- }
- else {
- badprint "Galera WsREP is disabled";
- }
-
- if ( defined( $mystat{'wsrep_connected'} )
- and $mystat{'wsrep_connected'} eq "ON" )
- {
- goodprint "Node is connected";
- }
- else {
- badprint "Node is disconnected";
- }
- if ( defined( $mystat{'wsrep_ready'} ) and $mystat{'wsrep_ready'} eq "ON" )
- {
- goodprint "Node is ready";
- }
- else {
- badprint "Node is not ready";
- }
- infoprint "Cluster status :" . $mystat{'wsrep_cluster_status'};
- if ( defined( $mystat{'wsrep_cluster_status'} )
- and $mystat{'wsrep_cluster_status'} eq "Primary" )
- {
- goodprint "Galera cluster is consistent and ready for operations";
- }
- else {
- badprint "Cluster is not consistent and ready";
- }
- if ( $mystat{'wsrep_local_state_uuid'} eq
- $mystat{'wsrep_cluster_state_uuid'} )
- {
- goodprint "Node and whole cluster at the same level: "
- . $mystat{'wsrep_cluster_state_uuid'};
- }
- else {
- badprint "Node and whole cluster not the same level";
- infoprint "Node state uuid: " . $mystat{'wsrep_local_state_uuid'};
- infoprint "Cluster state uuid: " . $mystat{'wsrep_cluster_state_uuid'};
- }
- if ( $mystat{'wsrep_local_state_comment'} eq 'Synced' ) {
- goodprint "Node is synced with whole cluster.";
- }
- else {
- badprint "Node is not synced";
- infoprint "Node State : " . $mystat{'wsrep_local_state_comment'};
- }
- if ( $mystat{'wsrep_local_cert_failures'} == 0 ) {
- goodprint "There is no certification failures detected.";
- }
- else {
- badprint "There is "
- . $mystat{'wsrep_local_cert_failures'}
- . " certification failure(s)detected.";
- }
-
- for my $key ( keys %mystat ) {
- if ( $key =~ /wsrep_|galera/i ) {
- debugprint "WSREP: $key = $mystat{$key}";
- }
- }
-
- #debugprint Dumper get_wsrep_options() if $opt{debug};
-}
-
-# Recommendations for InnoDB
-sub mysql_innodb {
- subheaderprint "InnoDB Metrics";
-
- # InnoDB
- unless ( defined $myvar{'have_innodb'}
- && $myvar{'have_innodb'} eq "YES" )
- {
- infoprint "InnoDB is disabled.";
- if ( mysql_version_ge( 5, 5 ) ) {
- my $defengine = 'InnoDB';
- $defengine = $myvar{'default_storage_engine'}
- if defined( $myvar{'default_storage_engine'} );
- badprint
-"InnoDB Storage engine is disabled. $defengine is the default storage engine"
- if $defengine eq 'InnoDB';
- infoprint
-"InnoDB Storage engine is disabled. $defengine is the default storage engine"
- if $defengine ne 'InnoDB';
- }
- return;
- }
- infoprint "InnoDB is enabled.";
- if ( !defined $enginestats{'InnoDB'} ) {
- if ( $opt{skipsize} eq 1 ) {
- infoprint "Skipped due to --skipsize option";
- return;
- }
- badprint "No tables are Innodb";
- $enginestats{'InnoDB'} = 0;
- }
-
- if ( $opt{buffers} ne 0 ) {
- infoprint "InnoDB Buffers";
- if ( defined $myvar{'innodb_buffer_pool_size'} ) {
- infoprint " +-- InnoDB Buffer Pool: "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . "";
- }
- if ( defined $myvar{'innodb_buffer_pool_instances'} ) {
- infoprint " +-- InnoDB Buffer Pool Instances: "
- . $myvar{'innodb_buffer_pool_instances'} . "";
- }
-
- if ( defined $myvar{'innodb_buffer_pool_chunk_size'} ) {
- infoprint " +-- InnoDB Buffer Pool Chunk Size: "
- . hr_bytes( $myvar{'innodb_buffer_pool_chunk_size'} ) . "";
- }
- if ( defined $myvar{'innodb_additional_mem_pool_size'} ) {
- infoprint " +-- InnoDB Additional Mem Pool: "
- . hr_bytes( $myvar{'innodb_additional_mem_pool_size'} ) . "";
- }
- if ( defined $myvar{'innodb_redo_log_capacity'} ) {
- infoprint " +-- InnoDB Redo Log Capacity: "
- . hr_bytes( $myvar{'innodb_redo_log_capacity'} );
- }
- else {
- if ( defined $myvar{'innodb_log_file_size'} ) {
- infoprint " +-- InnoDB Log File Size: "
- . hr_bytes( $myvar{'innodb_log_file_size'} );
- }
- if ( defined $myvar{'innodb_log_files_in_group'} ) {
- infoprint " +-- InnoDB Log File In Group: "
- . $myvar{'innodb_log_files_in_group'};
- infoprint " +-- InnoDB Total Log File Size: "
- . hr_bytes( $myvar{'innodb_log_files_in_group'} *
- $myvar{'innodb_log_file_size'} )
- . "("
- . $mycalc{'innodb_log_size_pct'}
- . " % of buffer pool)";
- }
- else {
- infoprint " +-- InnoDB Total Log File Size: "
- . hr_bytes( $myvar{'innodb_log_file_size'} ) . "("
- . $mycalc{'innodb_log_size_pct'}
- . " % of buffer pool)";
- }
- }
- if ( defined $myvar{'innodb_log_buffer_size'} ) {
- infoprint " +-- InnoDB Log Buffer: "
- . hr_bytes( $myvar{'innodb_log_buffer_size'} );
- }
- if ( defined $mystat{'Innodb_buffer_pool_pages_free'} ) {
- infoprint " +-- InnoDB Buffer Free: "
- . hr_bytes( $mystat{'Innodb_buffer_pool_pages_free'} ) . "";
- }
- if ( defined $mystat{'Innodb_buffer_pool_pages_total'} ) {
- infoprint " +-- InnoDB Buffer Used: "
- . hr_bytes( $mystat{'Innodb_buffer_pool_pages_total'} ) . "";
- }
- }
-
- if ( defined $myvar{'innodb_thread_concurrency'} ) {
- infoprint "InnoDB Thread Concurrency: "
- . $myvar{'innodb_thread_concurrency'};
- }
-
- # InnoDB Buffer Pool Size
- if ( $myvar{'innodb_file_per_table'} eq "ON" ) {
- goodprint "InnoDB File per table is activated";
- }
- else {
- badprint "InnoDB File per table is not activated";
- push( @adjvars, "innodb_file_per_table=ON" );
- }
-
- # InnoDB Buffer Pool Size
- if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} > 4294967295 ) {
- badprint
- "InnoDB Buffer Pool size limit reached for 32 bits architecture: ("
- . hr_bytes(4294967295) . " )";
- push( @adjvars,
- "limit innodb_buffer_pool_size under "
- . hr_bytes(4294967295)
- . " for 32 bits architecture" );
- }
- if ( $arch == 32 && $myvar{'innodb_buffer_pool_size'} < 4294967295 ) {
- goodprint "InnoDB Buffer Pool size ( "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " ) under limit for 32 bits architecture: ("
- . hr_bytes(4294967295) . ")";
- }
- if ( $arch == 64
- && $myvar{'innodb_buffer_pool_size'} > 18446744073709551615 )
- {
- badprint "InnoDB Buffer Pool size limit("
- . hr_bytes(18446744073709551615)
- . ") reached for 64 bits architecture";
- push( @adjvars,
- "limit innodb_buffer_pool_size under "
- . hr_bytes(18446744073709551615)
- . " for 64 bits architecture" );
- }
-
- if ( $arch == 64
- && $myvar{'innodb_buffer_pool_size'} < 18446744073709551615 )
- {
- goodprint "InnoDB Buffer Pool size ( "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " ) under limit for 64 bits architecture: ("
- . hr_bytes(18446744073709551615) . " )";
- }
- if ( $myvar{'innodb_buffer_pool_size'} > $enginestats{'InnoDB'} ) {
- goodprint "InnoDB buffer pool / data size: "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / "
- . hr_bytes( $enginestats{'InnoDB'} ) . "";
- }
- else {
- badprint "InnoDB buffer pool / data size: "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} ) . " / "
- . hr_bytes( $enginestats{'InnoDB'} ) . "";
- push( @adjvars,
- "innodb_buffer_pool_size (>= "
- . hr_bytes( $enginestats{'InnoDB'} )
- . ") if possible." );
- }
-
- # select round( 100* sum(allocated)/( select VARIABLE_VALUE
- # FROM information_schema.global_variables
- # where VARIABLE_NAME='innodb_buffer_pool_size' )
- # ,2) as "PCT ALLOC/BUFFER POOL"
- #from sys.x$innodb_buffer_stats_by_table;
-
- if ( $opt{experimental} ) {
- debugprint( 'innodb_buffer_alloc_pct: "'
- . $mycalc{innodb_buffer_alloc_pct}
- . '"' );
- if ( defined $mycalc{innodb_buffer_alloc_pct}
- and $mycalc{innodb_buffer_alloc_pct} ne '' )
- {
- if ( $mycalc{innodb_buffer_alloc_pct} < 80 ) {
- badprint "Ratio Buffer Pool allocated / Buffer Pool Size: "
- . $mycalc{'innodb_buffer_alloc_pct'} . '%';
- }
- else {
- goodprint "Ratio Buffer Pool allocated / Buffer Pool Size: "
- . $mycalc{'innodb_buffer_alloc_pct'} . '%';
- }
- }
- }
-
-# 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'} )
- {
- # Recalculate ratio if needed (ensure accuracy for modern systems)
- if ( defined $myvar{'innodb_buffer_pool_size'}
- && $myvar{'innodb_buffer_pool_size'} > 0 )
- {
- $mycalc{'innodb_log_size_pct'} =
- ( $myvar{'innodb_redo_log_capacity'} /
- $myvar{'innodb_buffer_pool_size'} ) * 100;
- }
-
- # New recommendation logic for MySQL >= 8.0.30
- 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;
-
- 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);
-
- 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.";
- }
-
- # Sanity check against total InnoDB data size
- 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.";
- }
- }
- }
- else {
- infoprint
-"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity.";
- }
- }
- elsif ( !mysql_version_ge( 8, 0, 30 ) ) {
-
- # Keep existing logic for older versions
- if ( $mycalc{'innodb_log_size_pct'} < 20
- or $mycalc{'innodb_log_size_pct'} > 30 )
- {
- if ( defined $myvar{'innodb_redo_log_capacity'} ) {
- badprint
- "Ratio InnoDB redo log capacity / InnoDB Buffer pool size ("
- . $mycalc{'innodb_log_size_pct'} . "%): "
- . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . " / "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " should be equal to 25%";
- push( @adjvars,
- "innodb_redo_log_capacity should be (="
- . hr_bytes_rnd( $myvar{'innodb_buffer_pool_size'} / 4 )
- . ") 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"
- );
- }
- else {
- 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'} . " / "
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " should be equal to 25%";
- push(
- @adjvars,
- "innodb_log_file_size should be (="
- . hr_bytes(
- (
- defined $myvar{'innodb_buffer_pool_size'}
- && $myvar{'innodb_buffer_pool_size'} ne ''
- ? $myvar{'innodb_buffer_pool_size'}
- : 0
- ) / (
- defined $myvar{'innodb_log_files_in_group'}
- && $myvar{'innodb_log_files_in_group'} ne ''
- && $myvar{'innodb_log_files_in_group'} != 0
- ? $myvar{'innodb_log_files_in_group'}
- : 1
- ) / 4
- )
- . ") 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"
- );
- }
- 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."
- );
- }
- }
- else {
- if ( defined $myvar{'innodb_redo_log_capacity'} ) {
- goodprint
- "Ratio InnoDB Redo Log Capacity / InnoDB Buffer pool size: "
- . hr_bytes( $myvar{'innodb_redo_log_capacity'} ) . "/"
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " should be equal to 25%";
- }
- else {
- push( @generalrec,
-"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: "
- . hr_bytes( $myvar{'innodb_log_file_size'} ) . " * "
- . $myvar{'innodb_log_files_in_group'} . "/"
- . hr_bytes( $myvar{'innodb_buffer_pool_size'} )
- . " should be equal to 25%";
- }
- }
- }
-
- # InnoDB Buffer Pool Instances (MySQL 5.6.6+)
- if ( not mysql_version_ge( 10, 4 )
- and defined( $myvar{'innodb_buffer_pool_instances'} ) )
- {
-
- # Bad Value if > 64
- if ( $myvar{'innodb_buffer_pool_instances'} > 64 ) {
- badprint "InnoDB buffer pool instances: "
- . $myvar{'innodb_buffer_pool_instances'} . "";
- push( @adjvars, "innodb_buffer_pool_instances (<= 64)" );
- }
-
- # InnoDB Buffer Pool Size > 1Go
- if ( $myvar{'innodb_buffer_pool_size'} > 1024 * 1024 * 1024 ) {
-
-# InnoDB Buffer Pool Size / 1Go = InnoDB Buffer Pool Instances limited to 64 max.
-
- # 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 );
-
- if ( $myvar{'innodb_buffer_pool_instances'} !=
- $max_innodb_buffer_pool_instances )
- {
- badprint "InnoDB buffer pool instances: "
- . $myvar{'innodb_buffer_pool_instances'} . "";
- push( @adjvars,
- "innodb_buffer_pool_instances(="
- . $max_innodb_buffer_pool_instances
- . ")" );
- }
- else {
- goodprint "InnoDB buffer pool instances: "
- . $myvar{'innodb_buffer_pool_instances'} . "";
- }
-
- # InnoDB Buffer Pool Size < 1Go
- }
- else {
- if ( $myvar{'innodb_buffer_pool_instances'} != 1 ) {
- badprint
-"InnoDB buffer pool <= 1G and Innodb_buffer_pool_instances(!=1).";
- push( @adjvars, "innodb_buffer_pool_instances (=1)" );
- }
- else {
- goodprint "InnoDB buffer pool instances: "
- . $myvar{'innodb_buffer_pool_instances'} . "";
- }
- }
- }
-
- # InnoDB Used Buffer Pool Size vs CHUNK size
- if (
- (
- ( $myvar{'version'} =~ /MariaDB/i )
- or ( $myvar{'version_comment'} =~ /MariaDB/i )
- )
- and mysql_version_ge( 10, 8 )
- and defined( $myvar{'innodb_buffer_pool_chunk_size'} )
- 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'} )
- || $myvar{'innodb_buffer_pool_chunk_size'} == 0
- || !defined( $myvar{'innodb_buffer_pool_size'} )
- || $myvar{'innodb_buffer_pool_size'} == 0
- || !defined( $myvar{'innodb_buffer_pool_instances'} )
- || $myvar{'innodb_buffer_pool_instances'} == 0 )
- {
-
- badprint
-"Cannot calculate InnoDB Buffer Pool Chunk breakdown due to missing or zero values:";
-
- infoprint " - innodb_buffer_pool_size: "
- . (
- defined $myvar{'innodb_buffer_pool_size'}
- ? $myvar{'innodb_buffer_pool_size'}
- : "undefined"
- );
- infoprint " - innodb_buffer_pool_chunk_size: "
- . (
- defined $myvar{'innodb_buffer_pool_chunk_size'}
- ? $myvar{'innodb_buffer_pool_chunk_size'}
- : "undefined"
- );
- infoprint " - innodb_buffer_pool_instances: "
- . (
- defined $myvar{'innodb_buffer_pool_instances'}
- ? $myvar{'innodb_buffer_pool_instances'}
- : "undefined"
- );
-
- }
- else {
- my $num_chunks = int( $myvar{'innodb_buffer_pool_size'} /
- $myvar{'innodb_buffer_pool_chunk_size'} );
- infoprint "Number of InnoDB Buffer Pool Chunk: $num_chunks for "
- . $myvar{'innodb_buffer_pool_instances'}
- . " Buffer Pool Instance(s)";
-
- my $expected_size = int( $myvar{'innodb_buffer_pool_chunk_size'} ) *
- int( $myvar{'innodb_buffer_pool_instances'} );
-
- if ( int( $myvar{'innodb_buffer_pool_size'} ) % $expected_size == 0 ) {
- goodprint
-"Innodb_buffer_pool_size aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances";
- }
- else {
- badprint
-"Innodb_buffer_pool_size not aligned with Innodb_buffer_pool_chunk_size & Innodb_buffer_pool_instances";
-
- push( @adjvars,
-"innodb_buffer_pool_size must always be equal to or a multiple of innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances"
- );
- }
- }
-
- # 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)";
- }
- elsif ( defined $mycalc{'pct_read_efficiency'}
- && $mycalc{'pct_read_efficiency'} < 90 )
- {
- badprint "InnoDB Read buffer efficiency: "
- . $mycalc{'pct_read_efficiency'} . "% ("
- . $mystat{'Innodb_buffer_pool_read_requests'}
- . " hits / "
- . ( $mystat{'Innodb_buffer_pool_reads'} +
- $mystat{'Innodb_buffer_pool_read_requests'} )
- . " total)";
- }
- else {
- goodprint "InnoDB Read buffer efficiency: "
- . $mycalc{'pct_read_efficiency'} . "% ("
- . $mystat{'Innodb_buffer_pool_read_requests'}
- . " hits / "
- . ( $mystat{'Innodb_buffer_pool_reads'} +
- $mystat{'Innodb_buffer_pool_read_requests'} )
- . " total)";
- }
-
- # 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)";
- }
- elsif ( defined $mycalc{'pct_write_efficiency'}
- && $mycalc{'pct_write_efficiency'} < 90 )
- {
- badprint "InnoDB Write Log efficiency: "
- . abs( $mycalc{'pct_write_efficiency'} ) . "% ("
- . abs( $mystat{'Innodb_log_write_requests'} -
- $mystat{'Innodb_log_writes'} )
- . " hits / "
- . $mystat{'Innodb_log_write_requests'}
- . " total)";
- push( @adjvars,
- "innodb_log_buffer_size (> "
- . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} )
- . ")" );
- }
- else {
- goodprint "InnoDB Write Log efficiency: "
- . $mycalc{'pct_write_efficiency'} . "% ("
- . ( $mystat{'Innodb_log_write_requests'} -
- $mystat{'Innodb_log_writes'} )
- . " hits / "
- . $mystat{'Innodb_log_write_requests'}
- . " total)";
- }
-
- # InnoDB Log Waits
- $mystat{'Innodb_log_waits_computed'} = 0;
-
- if ( defined( $mystat{'Innodb_log_waits'} )
- and defined( $mystat{'Innodb_log_writes'} )
- and $mystat{'Innodb_log_writes'} > 0.000001 )
- {
- $mystat{'Innodb_log_waits_computed'} =
- $mystat{'Innodb_log_waits'} / $mystat{'Innodb_log_writes'};
- }
- else {
- undef $mystat{'Innodb_log_waits_computed'};
- }
-
- if ( defined $mystat{'Innodb_log_waits_computed'}
- && $mystat{'Innodb_log_waits_computed'} > 0.000001 )
- {
- badprint "InnoDB log waits: "
- . percentage( $mystat{'Innodb_log_waits'},
- $mystat{'Innodb_log_writes'} )
- . "% ("
- . $mystat{'Innodb_log_waits'}
- . " waits / "
- . $mystat{'Innodb_log_writes'}
- . " writes)";
- push( @adjvars,
- "innodb_log_buffer_size (> "
- . hr_bytes_rnd( $myvar{'innodb_log_buffer_size'} )
- . ")" );
- }
- else {
- goodprint "InnoDB log waits: "
- . percentage( $mystat{'Innodb_log_waits'},
- $mystat{'Innodb_log_writes'} )
- . "% ("
- . $mystat{'Innodb_log_waits'}
- . " waits / "
- . $mystat{'Innodb_log_writes'}
- . " writes)";
- }
-
- # InnoDB Transaction Isolation and Metrics
- subheaderprint "InnoDB Transactions";
- my $isolation =
- $myvar{'transaction_isolation'}
- || $myvar{'tx_isolation'}
- || $myvar{'isolation_level'};
- if ( defined $isolation ) {
- infoprint("Transaction Isolation Level: $isolation");
- }
-
- if ( defined $myvar{'innodb_snapshot_isolation'} ) {
- infoprint( "InnoDB Snapshot Isolation: "
- . $myvar{'innodb_snapshot_isolation'} );
- if ( $myvar{'innodb_snapshot_isolation'} eq 'OFF'
- && ( $isolation || '' ) eq 'REPEATABLE-READ' )
- {
- badprint(
-"innodb_snapshot_isolation is OFF with REPEATABLE-READ (Stricter snapshot isolation is disabled)"
- );
- push( @adjvars, "innodb_snapshot_isolation=ON" );
- }
- }
-
- if ( defined $mycalc{'innodb_active_transactions'} ) {
- infoprint "Active InnoDB Transactions: "
- . $mycalc{'innodb_active_transactions'};
- }
- if ( defined $mycalc{'innodb_longest_transaction_duration'}
- && $mycalc{'innodb_longest_transaction_duration'} > 0 )
- {
- infoprint "Longest InnoDB Transaction Duration: "
- . pretty_uptime( $mycalc{'innodb_longest_transaction_duration'} );
- if ( $mycalc{'innodb_longest_transaction_duration'} > 3600 ) {
- badprint "Long running InnoDB transaction detected ("
- . pretty_uptime( $mycalc{'innodb_longest_transaction_duration'} )
- . ")";
- push( @generalrec,
-"Long running transactions can cause InnoDB history list length to increase and impact performance."
- );
- }
- }
-
- $result{'Calculations'} = {%mycalc};
-}
-
-sub mariadb_query_cache_info {
- subheaderprint "Query Cache Information";
-
- unless ( ( $myvar{'version'} =~ /MariaDB/i )
- or ( $myvar{'version_comment'} =~ /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'} ) {
- infoprint "innodb_stats_on_metadata: "
- . $myvar{'innodb_stats_on_metadata'};
- if ( $myvar{'innodb_stats_on_metadata'} eq 'ON' ) {
- badprint "Stat are updated during querying INFORMATION_SCHEMA.";
- push @adjvars, "SET innodb_stats_on_metadata = OFF";
-
- #Disabling innodb_stats_on_metadata
- select_one("SET GLOBAL innodb_stats_on_metadata = OFF;");
- return 1;
- }
- }
- goodprint "No stat updates during querying INFORMATION_SCHEMA.";
- return 0;
-}
-
-sub mysql_plugins {
- return if ( $opt{plugininfo} == 0 );
- subheaderprint "Plugin Information";
-
- my $query =
-"SELECT PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_STATUS, PLUGIN_TYPE FROM information_schema.PLUGINS WHERE PLUGIN_STATUS = 'ACTIVE' AND PLUGIN_TYPE != 'INFORMATION SCHEMA' ORDER BY PLUGIN_TYPE, PLUGIN_NAME";
- my @plugin_data = select_array($query);
-
- if (@plugin_data) {
- infoprint sprintf( "%-30s | %-10s | %-10s | %-20s",
- "Plugin", "Version", "Status", "Type" );
- infoprint "-" x 80;
- foreach my $line (@plugin_data) {
- my ( $name, $version, $status, $type ) = split( /\t/, $line );
- infoprint sprintf( "%-30s | %-10s | %-10s | %-20s",
- $name, $version, $status, $type );
- }
- }
- else {
- infoprint
-"No ACTIVE plugins found (excluding INFORMATION SCHEMA) in the information_schema.";
- }
-}
-
-# Recommendations for Database metrics
-sub mysql_databases {
- return if ( $opt{dbstat} == 0 );
-
- subheaderprint "Database Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Database metrics from information schema are missing in this version. Skipping...";
- return;
- }
-
- my $ignore_tables_sql = "";
- if ( $opt{'ignore-tables'} ) {
- my @ignored = split /,/, $opt{'ignore-tables'};
- $ignore_tables_sql =
- " AND TABLE_NAME NOT IN ('" . join( "','", @ignored ) . "')";
- }
-
- @dblist = select_array(
-"SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME NOT IN ( 'mysql', 'performance_schema', 'information_schema', 'sys' );"
- );
- infoprint "There is " . scalar(@dblist) . " Database(s).";
- my @totaldbinfo = split /\s/,
- select_one(
-"SELECT SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql;"
- );
- infoprint "All User Databases:";
- infoprint " +-- TABLE : "
- . select_one(
-"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql"
- ) . "";
- infoprint " +-- VIEW : "
- . select_one(
-"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql"
- ) . "";
- infoprint " +-- INDEX : "
- . select_one(
-"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql"
- ) . "";
-
- infoprint " +-- CHARS : "
- . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " ("
- . (
- join ", ",
- select_array(
-"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql;"
- )
- ) . ")";
- infoprint " +-- COLLA : "
- . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " ("
- . (
- join ", ",
- select_array(
-"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_COLLATION IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql;"
- )
- ) . ")";
- infoprint " +-- ROWS : "
- . ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] ) . "";
- infoprint " +-- DATA : "
- . hr_bytes( $totaldbinfo[1] ) . "("
- . percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%)";
- infoprint " +-- INDEX : "
- . hr_bytes( $totaldbinfo[2] ) . "("
- . percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%)";
- infoprint " +-- SIZE : " . hr_bytes( $totaldbinfo[3] ) . "";
- infoprint " +-- ENGINE: "
- . ( $totaldbinfo[6] eq 'NULL' ? 0 : $totaldbinfo[6] ) . " ("
- . (
- join ", ",
- select_array(
-"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE ENGINE IS NOT NULL AND TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')$ignore_tables_sql;"
- )
- ) . ")";
-
- $result{'Databases'}{'All databases'}{'Rows'} =
- ( $totaldbinfo[0] eq 'NULL' ? 0 : $totaldbinfo[0] );
- $result{'Databases'}{'All databases'}{'Data Size'} = $totaldbinfo[1];
- $result{'Databases'}{'All databases'}{'Data Pct'} =
- percentage( $totaldbinfo[1], $totaldbinfo[3] ) . "%";
- $result{'Databases'}{'All databases'}{'Index Size'} = $totaldbinfo[2];
- $result{'Databases'}{'All databases'}{'Index Pct'} =
- percentage( $totaldbinfo[2], $totaldbinfo[3] ) . "%";
- $result{'Databases'}{'All databases'}{'Total Size'} = $totaldbinfo[3];
- print "\n" unless ( $opt{'silent'} or $opt{'json'} );
- my $nbViews = 0;
- my $nbTables = 0;
-
- foreach (@dblist) {
- my @dbinfo = split /\s/,
- select_one(
-"SELECT TABLE_SCHEMA, SUM(TABLE_ROWS), SUM(DATA_LENGTH), SUM(INDEX_LENGTH), SUM(DATA_LENGTH+INDEX_LENGTH), COUNT(DISTINCT ENGINE), COUNT(TABLE_NAME), COUNT(DISTINCT(TABLE_COLLATION)), COUNT(DISTINCT(ENGINE)) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_'$ignore_tables_sql GROUP BY TABLE_SCHEMA ORDER BY TABLE_SCHEMA"
- );
- next unless defined $dbinfo[0];
-
- infoprint "Database: " . $dbinfo[0] . "";
- $nbTables = select_one(
-"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$_'$ignore_tables_sql"
- );
- infoprint " +-- TABLE : $nbTables";
- infoprint " +-- VIEW : "
- . select_one(
-"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='VIEW' AND TABLE_SCHEMA='$_'$ignore_tables_sql"
- ) . "";
- infoprint " +-- INDEX : "
- . select_one(
-"SELECT count(distinct(concat(TABLE_NAME, TABLE_SCHEMA, INDEX_NAME))) from information_schema.STATISTICS WHERE TABLE_SCHEMA='$_'$ignore_tables_sql"
- ) . "";
- infoprint " +-- CHARS : "
- . ( $totaldbinfo[5] eq 'NULL' ? 0 : $totaldbinfo[5] ) . " ("
- . (
- join ", ",
- select_array(
-"select distinct(CHARACTER_SET_NAME) from information_schema.columns WHERE CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA='$_'$ignore_tables_sql;"
- )
- ) . ")";
- infoprint " +-- COLLA : "
- . ( $dbinfo[7] eq 'NULL' ? 0 : $dbinfo[7] ) . " ("
- . (
- join ", ",
- select_array(
-"SELECT DISTINCT(TABLE_COLLATION) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND TABLE_COLLATION IS NOT NULL$ignore_tables_sql;"
- )
- ) . ")";
- infoprint " +-- ROWS : "
- . ( !defined( $dbinfo[1] ) or $dbinfo[1] eq 'NULL' ? 0 : $dbinfo[1] )
- . "";
- infoprint " +-- DATA : "
- . hr_bytes( $dbinfo[2] ) . "("
- . percentage( $dbinfo[2], $dbinfo[4] ) . "%)";
- infoprint " +-- INDEX : "
- . hr_bytes( $dbinfo[3] ) . "("
- . percentage( $dbinfo[3], $dbinfo[4] ) . "%)";
- infoprint " +-- TOTAL : " . hr_bytes( $dbinfo[4] ) . "";
- infoprint " +-- ENGINE: "
- . ( $dbinfo[8] eq 'NULL' ? 0 : $dbinfo[8] ) . " ("
- . (
- join ", ",
- select_array(
-"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL$ignore_tables_sql"
- )
- ) . ")";
-
- foreach my $eng (
- select_array(
-"SELECT DISTINCT(ENGINE) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$_' AND ENGINE IS NOT NULL$ignore_tables_sql"
- )
- )
- {
- infoprint " +-- ENGINE $eng : "
- . select_one(
-"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbinfo[0]' AND ENGINE='$eng'$ignore_tables_sql"
- ) . " TABLE(s)";
- }
-
- if ( $nbTables == 0 ) {
- badprint " No table in $dbinfo[0] database";
- next;
- }
- badprint "Index size is larger than data size for $dbinfo[0] \n"
- if ( $dbinfo[2] ne 'NULL' )
- and ( $dbinfo[3] ne 'NULL' )
- and ( $dbinfo[2] < $dbinfo[3] );
- if ( $dbinfo[5] > 1 and $nbTables > 0 ) {
- badprint "There are "
- . $dbinfo[5]
- . " storage engines. Be careful. \n";
- push @generalrec,
-"Select one storage engine (InnoDB is a good choice) for all tables in $dbinfo[0] database ($dbinfo[5] engines detected)";
- }
- $result{'Databases'}{ $dbinfo[0] }{'Rows'} = $dbinfo[1];
- $result{'Databases'}{ $dbinfo[0] }{'Tables'} = $dbinfo[6];
- $result{'Databases'}{ $dbinfo[0] }{'Collations'} = $dbinfo[7];
- $result{'Databases'}{ $dbinfo[0] }{'Data Size'} = $dbinfo[2];
- $result{'Databases'}{ $dbinfo[0] }{'Data Pct'} =
- percentage( $dbinfo[2], $dbinfo[4] ) . "%";
- $result{'Databases'}{ $dbinfo[0] }{'Index Size'} = $dbinfo[3];
- $result{'Databases'}{ $dbinfo[0] }{'Index Pct'} =
- percentage( $dbinfo[3], $dbinfo[4] ) . "%";
- $result{'Databases'}{ $dbinfo[0] }{'Total Size'} = $dbinfo[4];
-
- if ( $dbinfo[7] > 1 ) {
- badprint $dbinfo[7]
- . " different collations for database "
- . $dbinfo[0];
- push( @generalrec,
- "Check all table collations are identical for all tables in "
- . $dbinfo[0]
- . " database." );
- }
- else {
- goodprint $dbinfo[7]
- . " collation for "
- . $dbinfo[0]
- . " database.";
- }
- if ( $dbinfo[8] > 1 ) {
- badprint $dbinfo[8]
- . " different engines for database "
- . $dbinfo[0];
- push( @generalrec,
- "Check all table engines are identical for all tables in "
- . $dbinfo[0]
- . " database." );
- }
- else {
- goodprint $dbinfo[8] . " engine for " . $dbinfo[0] . " database.";
- }
-
- my @distinct_column_charset = select_array(
-"select DISTINCT(CHARACTER_SET_NAME) from information_schema.COLUMNS where CHARACTER_SET_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND CHARACTER_SET_NAME IS NOT NULL"
- );
- infoprint "Charsets for $dbinfo[0] database table column: "
- . join( ', ', @distinct_column_charset );
- if ( scalar(@distinct_column_charset) > 1 ) {
- badprint $dbinfo[0]
- . " table column(s) has several charsets defined for all text like column(s).";
- push( @generalrec,
- "Limit charset for column to one charset if possible for "
- . $dbinfo[0]
- . " database." );
- }
- else {
- goodprint $dbinfo[0]
- . " table column(s) has same charset defined for all text like column(s).";
- }
-
- my @distinct_column_collation = select_array(
-"select DISTINCT(COLLATION_NAME) from information_schema.COLUMNS where COLLATION_NAME IS NOT NULL AND TABLE_SCHEMA ='$_' AND COLLATION_NAME IS NOT NULL"
- );
- infoprint "Collations for $dbinfo[0] database table column: "
- . join( ', ', @distinct_column_collation );
- if ( scalar(@distinct_column_collation) > 1 ) {
- badprint $dbinfo[0]
- . " table column(s) has several collations defined for all text like column(s).";
- push( @generalrec,
- "Limit collations for column to one collation if possible for "
- . $dbinfo[0]
- . " database." );
- }
- else {
- goodprint $dbinfo[0]
- . " table column(s) has same collation defined for all text like column(s).";
- }
- }
-}
-
-# Recommendations for database columns
-sub mysql_tables {
- return if ( $opt{tbstat} == 0 );
-
- subheaderprint "Table Column Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Table column metrics from information schema are missing in this version. Skipping...";
- return;
- }
- if ( mysql_version_ge(8) and not mysql_version_eq(10) ) {
- infoprint
-"MySQL and Percona version 8.0 and greater have removed PROCEDURE ANALYSE feature";
- $opt{colstat} = 0;
- infoprint "Disabling colstat parameter";
-
- }
-
- my $ignore_tables_sql = "";
- if ( $opt{'ignore-tables'} ) {
- my @ignored = split /,/, $opt{'ignore-tables'};
- $ignore_tables_sql =
- " AND TABLE_NAME NOT IN ('" . join( "','", @ignored ) . "')";
- }
-
- my $schema_doc = "";
- my $mermaid_er = "";
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- $schema_doc = "# Database Schema Documentation\n\n";
- $schema_doc .=
- "Generated by MySQLTuner on " . scalar(localtime) . "\n\n";
- $mermaid_er = "## Visual Database Schema (Mermaid)\n\n";
- $mermaid_er .= "```mermaid\nerDiagram\n";
- }
-
- if ( $opt{schemadir} ) {
- $opt{schemadir} = abs_path( $opt{schemadir} );
- if ( !-d $opt{schemadir} ) {
- mkdir $opt{schemadir}
- or die "Cannot create directory $opt{schemadir}: $!";
- }
- }
-
- foreach ( select_user_dbs() ) {
- my $dbname = $_;
- next unless defined $_;
-
- my $current_schema_doc = "";
- my $current_mermaid_er = "";
- if ( $opt{schemadir} ) {
- $current_schema_doc = "# Database: $dbname\n\n";
- $current_schema_doc .=
- "Generated by MySQLTuner on " . scalar(localtime) . "\n\n";
- $current_mermaid_er = "## Visual Database Schema (Mermaid)\n\n";
- $current_mermaid_er .= "```mermaid\nerDiagram\n";
- }
-
- infoprint "Database: " . $_ . "";
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- $schema_doc .= "## Database: $dbname\n\n";
- $current_schema_doc .= "### Tables\n\n" if $opt{schemadir} ne '';
- }
-
- my @dbtable = select_array(
-"SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$dbname' AND TABLE_TYPE='BASE TABLE'$ignore_tables_sql ORDER BY TABLE_NAME"
- );
- foreach (@dbtable) {
- my $tbname = $_;
- infoprint " +-- TABLE: $tbname";
- my $engine = select_one(
-"SELECT ENGINE FROM information_schema.tables where TABLE_schema='$dbname' AND TABLE_NAME='$tbname'"
- );
- infoprint " +-- TYPE: $engine";
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- my $table_info = "### Table: $tbname\n";
- $table_info .= "- **Engine**: $engine\n\n";
- $table_info .= "#### Indexes\n";
-
- $schema_doc .= $table_info;
- $current_schema_doc .= $table_info if $opt{schemadir} ne '';
-
- $mermaid_er .= " $tbname {\n";
- $current_mermaid_er .= " $tbname {\n"
- if $opt{schemadir} ne '';
- }
-
- my $selIdxReq = <<"ENDSQL";
- SELECT index_name AS idxname,
- GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols,
- INDEX_TYPE as type
- FROM information_schema.statistics
- WHERE INDEX_SCHEMA='$dbname'
- AND TABLE_NAME='$tbname'
- GROUP BY idxname, type
-ENDSQL
- my @tbidx = select_array($selIdxReq);
- my $found = 0;
- foreach my $idx (@tbidx) {
- my @info = split /\s/, $idx;
- next if $info[0] eq 'NULL';
- infoprint
- " +-- Index $info[0] - Cols: $info[1] - Type: $info[2]";
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- my $idx_info = "- **$info[0]**: $info[1] ($info[2])\n";
- $schema_doc .= $idx_info;
- $current_schema_doc .= $idx_info if $opt{schemadir} ne '';
- }
- $found++;
- }
- if ( $found == 0 ) {
- badprint("Table $dbname.$tbname has no index defined");
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- $schema_doc .= "- *No indexes defined*\n";
- $current_schema_doc .= "- *No indexes defined*\n"
- if $opt{schemadir} ne '';
- }
- push @generalrec,
- "Add at least a primary key on table $dbname.$tbname";
- }
-
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- $schema_doc .= "\n#### Columns\n";
- $current_schema_doc .= "\n#### Columns\n"
- if $opt{schemadir} ne '';
- }
-
- my @tbcol = select_array(
-"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname'"
- );
- foreach (@tbcol) {
- my $ctype = select_one(
-"SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' "
- );
- my $isnull = select_one(
-"SELECT IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$tbname' AND COLUMN_NAME='$_' "
- );
-
- my $current_type =
- uc($ctype) . ( $isnull eq 'NO' ? " NOT NULL" : " NULL" );
- my $optimal_type = '';
- infoprint " +-- Column $tbname.$_: $current_type";
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- my $col_info = "- **$_**: $current_type\n";
- $schema_doc .= $col_info;
- $current_schema_doc .= $col_info if $opt{schemadir} ne '';
-
- my $mtype = $ctype;
- $mtype =~ s/\(.*\)//g; # Strip lengths for Mermaid
- $mermaid_er .= " $mtype $_\n";
- $current_mermaid_er .= " $mtype $_\n"
- if $opt{schemadir} ne '';
- }
- if ( $opt{colstat} == 1 ) {
- $optimal_type = select_str_g( "Optimal_fieldtype",
-"SELECT \\`$_\\` FROM \\`$dbname\\`.\\`$tbname\\` PROCEDURE ANALYSE(100000)"
- )
- unless ( mysql_version_ge(8)
- and not mysql_version_eq(10) );
- }
- if ( $optimal_type eq '' ) {
-
- #infoprint " +-- Current Fieldtype: $current_type";
-
- #infoprint " Optimal Fieldtype: Not available";
- }
- elsif ( $current_type ne $optimal_type
- and $current_type !~ /.*DATETIME.*/
- and $current_type !~ /.*TIMESTAMP.*/ )
- {
- infoprint " +-- Current Fieldtype: $current_type";
- if ( $optimal_type =~ /.*ENUM\(.*/ ) {
- $optimal_type = "ENUM( ... )";
- }
- infoprint " +-- Optimal Fieldtype: $optimal_type ";
- if ( $optimal_type !~ /.*ENUM\(.*/ ) {
- badprint
-"Consider changing type for column $_ in table $dbname.$tbname";
- push( @generalrec,
-"ALTER TABLE \`$dbname\`.\`$tbname\` MODIFY \`$_\` $optimal_type;"
- );
- }
- }
- else {
- goodprint "$dbname.$tbname ($_) type: $current_type";
- }
- }
- if ( $opt{dumpdir} or $opt{schemadir} ) {
- $schema_doc .= "\n---\n\n";
- $current_schema_doc .= "\n---\n\n" if $opt{schemadir} ne '';
-
- $mermaid_er .= " }\n";
- $current_mermaid_er .= " }\n" if $opt{schemadir} ne '';
- }
- }
-
- if ( $opt{schemadir} ) {
- $current_mermaid_er .= "```\n\n";
- $current_schema_doc .= $current_mermaid_er;
- my $doc_file = "$opt{schemadir}/$dbname.md";
- if ( open( my $fh, '>', $doc_file ) ) {
- binmode( $fh, ":utf8" );
- print $fh $current_schema_doc;
- close($fh);
- infoprint
- "Schema documentation for $dbname generated in $doc_file";
- }
- else {
- badprint
-"Could not write schema documentation for $dbname to $doc_file: $!";
- }
- }
- }
- if ( $opt{dumpdir} ne '' && $opt{dumpdir} ne '0' ) {
- $mermaid_er .= "```\n\n";
- $schema_doc .= $mermaid_er;
- my $doc_file = "$opt{dumpdir}/schema_documentation.md";
- if ( open( my $fh, '>', $doc_file ) ) {
- binmode( $fh, ":utf8" );
- print $fh $schema_doc;
- close($fh);
- infoprint
- "Consolidated schema documentation generated in $doc_file";
- }
- else {
- badprint
-"Could not write consolidated schema documentation to $doc_file: $!";
- }
- }
-}
-
-# Recommendations for Indexes metrics
-sub mysql_indexes {
- return if ( $opt{idxstat} == 0 );
-
- subheaderprint "Indexes Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Index metrics from information schema are missing in this version. Skipping...";
- return;
- }
-
-# unless ( mysql_version_ge( 5, 6 ) ) {
-# infoprint
-#"Skip Index metrics from information schema due to erroneous information provided in this version";
-# return;
-# }
- my $ignore_tables_sql = "";
- if ( $opt{'ignore-tables'} ) {
- my @ignored = split /,/, $opt{'ignore-tables'};
- $ignore_tables_sql =
- " AND TABLE_NAME NOT IN ('" . join( "','", @ignored ) . "')";
- }
-
- my $selIdxReq = <<"ENDSQL";
-SELECT
- CONCAT(t.TABLE_SCHEMA, '.', t.TABLE_NAME) AS 'table',
- CONCAT(s.INDEX_NAME, '(', s.COLUMN_NAME, ')') AS 'index'
- , s.SEQ_IN_INDEX AS 'seq'
- , s2.max_columns AS 'maxcol'
- , s.CARDINALITY AS 'card'
- , t.TABLE_ROWS AS 'est_rows'
- , INDEX_TYPE as type
- , ROUND(((s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) * 100), 2) AS 'sel'
-FROM INFORMATION_SCHEMA.STATISTICS s
- INNER JOIN INFORMATION_SCHEMA.TABLES t
- ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
- AND s.TABLE_NAME = t.TABLE_NAME
- INNER JOIN (
- SELECT
- TABLE_SCHEMA
- , TABLE_NAME
- , INDEX_NAME
- , MAX(SEQ_IN_INDEX) AS max_columns
- FROM INFORMATION_SCHEMA.STATISTICS
- WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema')$ignore_tables_sql
- AND INDEX_TYPE <> 'FULLTEXT'
- GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
- ) AS s2
- ON s.TABLE_SCHEMA = s2.TABLE_SCHEMA
- AND s.TABLE_NAME = s2.TABLE_NAME
- AND s.INDEX_NAME = s2.INDEX_NAME
-WHERE t.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema')$ignore_tables_sql
-AND t.TABLE_ROWS > 10
-AND s.CARDINALITY IS NOT NULL
-AND (s.CARDINALITY / IFNULL(t.TABLE_ROWS, 0.01)) < 8.00
-ORDER BY sel
-LIMIT 10;
-ENDSQL
- my @idxinfo = select_array($selIdxReq);
- infoprint "Worst selectivity indexes:";
- foreach (@idxinfo) {
- debugprint "$_";
- my @info = split /\s/;
- infoprint "Index: " . $info[1] . "";
-
- infoprint " +-- COLUMN : " . $info[0] . "";
- infoprint " +-- NB SEQS : " . $info[2] . " sequence(s)";
- infoprint " +-- NB COLS : " . $info[3] . " column(s)";
- infoprint " +-- CARDINALITY : " . $info[4] . " distinct values";
- infoprint " +-- NB ROWS : " . $info[5] . " rows";
- infoprint " +-- TYPE : " . $info[6];
- infoprint " +-- SELECTIVITY : " . $info[7] . "%";
-
- $result{'Indexes'}{ $info[1] }{'Column'} = $info[0];
- $result{'Indexes'}{ $info[1] }{'Sequence number'} = $info[2];
- $result{'Indexes'}{ $info[1] }{'Number of column'} = $info[3];
- $result{'Indexes'}{ $info[1] }{'Cardinality'} = $info[4];
- $result{'Indexes'}{ $info[1] }{'Row number'} = $info[5];
- $result{'Indexes'}{ $info[1] }{'Index Type'} = $info[6];
- $result{'Indexes'}{ $info[1] }{'Selectivity'} = $info[7];
- if ( $info[7] < 25 ) {
- badprint "$info[1] has a low selectivity";
- }
- }
- infoprint "Indexes per database:";
- foreach my $dbname ( select_user_dbs() ) {
- infoprint "Database: " . $dbname . "";
- $selIdxReq = <<"ENDSQL";
- SELECT concat(table_name, '.', index_name) AS idxname,
- GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols,
- SUM(CARDINALITY) as card,
- INDEX_TYPE as type
- FROM information_schema.statistics
- WHERE INDEX_SCHEMA='$dbname'
- AND index_name IS NOT NULL$ignore_tables_sql
- GROUP BY table_name, idxname, type
-ENDSQL
- my $found = 0;
- foreach my $idxinfo ( select_array($selIdxReq) ) {
- my @info = split /\s/, $idxinfo;
- next if $info[0] eq 'NULL';
- infoprint " +-- INDEX : " . $info[0];
- infoprint " +-- COLUMNS : " . $info[1];
- infoprint " +-- CARDINALITY: " . $info[2];
- infoprint " +-- TYPE : " . $info[4] if defined $info[4];
- infoprint " +-- COMMENT : " . $info[5] if defined $info[5];
- $found++;
- }
- my $nbTables = select_one(
-"SELECT count(*) from information_schema.TABLES WHERE TABLE_TYPE ='BASE TABLE' AND TABLE_SCHEMA='$dbname'"
- );
- badprint "No index found for $dbname database"
- if $found == 0 and $nbTables > 1;
- push @generalrec, "Add indexes on tables from $dbname database"
- if $found == 0 and $nbTables > 1;
- }
- return
- unless ( defined( $myvar{'performance_schema'} )
- and $myvar{'performance_schema'} eq 'ON' );
-
- $selIdxReq = <<"ENDSQL";
-SELECT CONCAT(object_schema, '.', object_name) AS 'table', index_name
-FROM performance_schema.table_io_waits_summary_by_index_usage
-WHERE index_name IS NOT NULL
-AND count_star = 0
-AND index_name <> 'PRIMARY'
-AND object_schema NOT IN ('mysql', 'performance_schema', 'information_schema')$ignore_tables_sql
-ORDER BY count_star, object_schema, object_name;
-ENDSQL
- @idxinfo = select_array($selIdxReq);
- infoprint "Unused indexes:";
- push( @generalrec, "Remove unused indexes." ) if ( scalar(@idxinfo) > 0 );
- foreach (@idxinfo) {
- debugprint "$_";
- my @info = split /\s/;
- badprint "Index: $info[1] on $info[0] is not used.";
- push @{ $result{'Indexes'}{'Unused Indexes'} },
- $info[0] . "." . $info[1];
- }
-}
-
-sub mysql_views {
- subheaderprint "Views Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Views metrics from information schema are missing in this version. Skipping...";
- return;
- }
-}
-
-sub mysql_routines {
- subheaderprint "Routines Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Routines metrics from information schema are missing in this version. Skipping...";
- return;
- }
-}
-
-sub mysql_triggers {
- subheaderprint "Triggers Metrics";
- unless ( mysql_version_ge( 5, 5 ) ) {
- infoprint
-"Trigger metrics from information schema are missing in this version. Skipping...";
- return;
- }
-}
-
-# Take the two recommendation arrays and display them at the end of the output
-sub make_recommendations {
- $result{'Recommendations'} = \@generalrec;
- $result{'AdjustVariables'} = \@adjvars;
- $result{'Modeling'} = \@modeling;
-
- # Modular structure for modern reporting
- $result{'Modules'} = {
- 'System' => \@sysrec,
- 'Performance' => \@adjvars,
- 'Modeling' => \@modeling,
- 'Security' => \@secrec,
- };
- subheaderprint "Recommendations";
- if ( @generalrec > 0 ) {
- prettyprint "General recommendations:";
- foreach (@generalrec) { prettyprint " " . $_ . ""; }
- }
- if ( @adjvars > 0 ) {
- prettyprint "Variables to adjust:";
- if ( $mycalc{'pct_max_physical_memory'} > 90 ) {
- prettyprint
- " *** MySQL's maximum memory usage is dangerously high ***\n"
- . " *** Add RAM before increasing MySQL buffer variables ***";
- }
- foreach (@adjvars) { prettyprint " " . $_ . ""; }
- }
- if ( @generalrec == 0 && @adjvars == 0 ) {
- prettyprint "No additional performance recommendations are available.";
- }
-}
-
-sub close_outputfile {
- close($fh) if defined($fh);
-}
-
-sub headerprint {
- prettyprint " >> MySQLTuner $tunerversion\n"
- . "\t * Jean-Marie Renouard \n"
- . "\t * Major Hayden \n"
- . " >> Bug reports, feature requests, and downloads at http://mysqltuner.pl/\n"
- . " >> Run with '--help' for additional options and output filtering";
- debugprint( "Debug: " . $opt{debug} );
- debugprint( "Experimental: " . $opt{experimental} );
-}
-
-sub string2file {
- my $filename = shift;
- my $content = shift;
- open my $fh, q(>), $filename
- or die
-"Unable to open $filename in write mode. Please check permissions for this file or directory";
- print $fh $content if defined($content);
- close $fh;
- debugprint $content;
-}
-
-sub file2array {
- my $filename = shift;
- debugprint "* reading $filename";
- my $fh;
- open( $fh, q(<), "$filename" )
- or die "Couldn't open $filename for reading: $!\n";
- my @lines = <$fh>;
- close($fh);
- return @lines;
-}
-
-sub file2string {
- return join( '', file2array(@_) );
-}
-
-my $templateModel;
-if ( $opt{'template'} ne 0 ) {
- $templateModel = file2string( $opt{'template'} );
-}
-else {
- # DEFAULT REPORT TEMPLATE
- $templateModel = <<'END_TEMPLATE';
-
-
-
- MySQLTuner Report
-
-
-
-
-Result output
-
-{$data}
-
-
-
-
-END_TEMPLATE
-}
-
-sub dump_result {
-
- #debugprint Dumper( \%result ) if ( $opt{'debug'} );
- debugprint "HTML REPORT: $opt{'reportfile'}";
-
- if ( $opt{'reportfile'} ne 0 ) {
- eval { require Text::Template };
- eval { require JSON };
- if ($@) {
- badprint "Text::Template Module is needed.";
- die "Text::Template Module is needed.";
- }
-
- my $json = JSON->new->allow_nonref;
- my $json_text = $json->pretty->encode( \%result );
- my %vars = (
- 'data' => \%result,
- 'debug' => $json_text,
- );
- my $template;
- {
- no warnings 'once';
- $template = Text::Template->new(
- TYPE => 'STRING',
- PREPEND => q{;},
- SOURCE => $templateModel,
- DELIMITERS => [ '[%', '%]' ]
- ) or die "Couldn't construct template: $Text::Template::ERROR";
- }
-
- open my $fh, q(>), $opt{'reportfile'}
- or die
-"Unable to open $opt{'reportfile'} in write mode. please check permissions for this file or directory";
- $template->fill_in( HASH => \%vars, OUTPUT => $fh );
- close $fh;
- }
-
- if ( $opt{'json'} ne 0 ) {
- eval { require JSON };
- if ($@) {
- print "$bad JSON Module is needed.\n";
- return 1;
- }
-
- my $json = JSON->new->allow_nonref;
- print $json->utf8(1)->pretty( ( $opt{'prettyjson'} ? 1 : 0 ) )
- ->encode( \%result );
-
- if ( $opt{'outputfile'} ne 0 ) {
- unlink $opt{'outputfile'} if ( -e $opt{'outputfile'} );
- 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 ) )
- ->encode( \%result );
- close $fh;
- }
- }
-}
-
-sub which {
- my $prog_name = shift;
- my $path_string = shift;
- my @path_array = split /:/, $ENV{'PATH'};
- if ($is_win) { @path_array = split /;/, $ENV{'PATH'} =~ s/\\/\//gr; }
-
- for my $path (@path_array) {
- if ($is_win) {
- return "$path/$prog_name.exe" if ( -x "$path/$prog_name.exe" );
- return "$path/$prog_name.com" if ( -x "$path/$prog_name.com" );
- return "$path/$prog_name.bat" if ( -x "$path/$prog_name.bat" );
- }
- else {
- return "$path/$prog_name" if ( -x "$path/$prog_name" );
- }
- }
-
- return 0;
-}
-
-sub dump_csv_files {
- return if ( ( $opt{dumpdir} // '0' ) eq '0' or $opt{dumpdir} eq '' );
-
- subheaderprint "Dumping CSV files";
-
- $opt{dumpdir} = abs_path( $opt{dumpdir} );
- if ( !-d $opt{dumpdir} ) {
- mkdir $opt{dumpdir} or die "Cannot create directory $opt{dumpdir}: $!";
- }
-
- infoprint("Dumpdir: $opt{dumpdir}");
-
- # Always create raw_mysqltuner.txt in dumpdir for complete analysis output
- # This is independent of --outputfile option
- my $raw_output_file = "$opt{dumpdir}/raw_mysqltuner.txt";
- infoprint("Auto-generating raw output file: $raw_output_file");
-
- # If outputfile is not already set, use raw_mysqltuner.txt as the main output
- if ( $opt{outputfile} eq 0 ) {
- $opt{outputfile} = $raw_output_file;
- my $outputfile_path = abs_path( $opt{outputfile} );
- open( $fh, '>', $outputfile_path )
- or die("Failed to open $outputfile_path for writing: $!");
- $opt{nocolor} = 1; # Disable colors in file output
- }
-
- # If outputfile is already set, create a second file handle for raw output
- else {
- my $raw_fh;
- open( $raw_fh, '>', $raw_output_file )
- or die("Failed to open $raw_output_file for writing: $!");
-
- # Duplicate all output to both file handles
- # We'll need to modify prettyprint to write to both $fh and $raw_fh
- # For now, just create a symlink
- close($raw_fh);
- unlink($raw_output_file);
- my $target = abs_path( $opt{outputfile} );
- symlink( $target, $raw_output_file )
- or warn("Could not create symlink $raw_output_file -> $target: $!");
- }
-
- # Store all sys schema in dumpdir if defined
- infoprint("Dumping sys schema");
- for my $sys_view ( select_array('use sys;show tables;') ) {
- if ( $sys_view =~ /innodb_buffer_stats/ ) {
- infoprint("SKIPPING $sys_view");
- next;
- }
- infoprint "Dumping $sys_view into $opt{dumpdir}";
- my $sys_view_table = $sys_view;
- $sys_view_table =~ s/\$/\\\$/g;
- select_csv_file( "$opt{dumpdir}/sys_$sys_view.csv",
- 'select * from sys.\`' . $sys_view_table . '\`' );
- }
-
- # Store all information schema in dumpdir if defined
- infoprint("Dumping information schema");
- for my $info_s_table ( select_array('use information_schema;show tables;') )
- {
- next if $info_s_table =~ /INNODB_BUFFER_PAGE/;
- infoprint "Dumping $info_s_table into $opt{dumpdir}";
- select_csv_file(
- "$opt{dumpdir}/ifs_${info_s_table}.csv",
- "select * from information_schema.$info_s_table"
- );
- }
-
- # Store all performance schema in dumpdir if defined
- infoprint("Dumping performance schema");
- for
- my $info_pf_table ( select_array('use performance_schema;show tables;') )
- {
- next if $info_pf_table =~ /^events_/;
- infoprint
- "Performance Schema Dumping $info_pf_table into $opt{dumpdir}";
- select_csv_file(
- "$opt{dumpdir}/ps_${info_pf_table}.csv",
- "select * from performance_schema.$info_pf_table"
- );
- }
-}
-
-# ---------------------------------------------------------------------------
-# BEGIN 'MAIN'
-# ---------------------------------------------------------------------------
-if ( !caller ) {
- parse_cli_args; # Parse CLI arguments
- setup_environment; # Initialize variables and handle early exits
- headerprint; # Header Print
-
- validate_tuner_version; # Check latest version
- cloud_setup;
- mysql_setup; # Gotta login first
- debugprint "MySQL FINAL Client : $mysqlcmd $mysqllogin";
- debugprint "MySQL Admin FINAL Client : $mysqladmincmd $mysqllogin";
-
- dump_csv_files; # dump csv files
- os_setup; # Set up some OS variables
- get_all_vars; # Toss variables/status into hashes
- get_tuning_info; # Get information about the tuning connection
- calculations; # Calculate everything we need
- check_architecture; # Suggest 64-bit upgrade
- check_storage_engines; # Show enabled storage engines
- if ( $opt{'feature'} ) {
- subheaderprint "See FEATURES.md for more information";
- no strict 'refs';
- for my $feature ( split /,/, $opt{'feature'} ) {
- subheaderprint "Running feature: $feature";
- $feature->();
- }
- make_recommendations;
- goodprint "Terminated successfully";
- exit(0);
- }
- validate_mysql_version; # Check current MySQL version
-
- system_recommendations; # Avoid too many services on the same host
- log_file_recommendations; # check log file content
- check_metadata_perf; # Show parameter impacting performance during analysis
- mysql_databases; # Show information about databases
- mysql_tables; # Show information about table column
- mysql_table_structures; # Show information about table structures
-
- mysql_indexes; # Show information about indexes
- mysql_views; # Show information about views
- mysql_triggers; # Show information about triggers
- mysql_routines; # Show information about routines
- security_recommendations; # Display some security recommendations
- ssl_tls_recommendations; # Display SSL/TLS recommendations
- cve_recommendations; # Display related CVE
- mysql_plugins; # Print Plugin Information
-
- mysql_stats; # Print the server stats
- mysql_pfs; # Print Performance schema info
-
- 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
-
- #mariadb_rockdb; # Print MariaDB RockDB stats
- #mariadb_spider; # Print MariaDB Spider stats
- #mariadb_connect; # Print MariaDB Connect stats
- mariadb_galera; # Print MariaDB Galera Cluster stats
- get_replication_status; # Print replication info
- make_recommendations; # Make recommendations based on stats
- dump_result; # Dump result if debug is on
- goodprint "Terminated successfully";
- close_outputfile; # Close reportfile if needed
-
- # ---------------------------------------------------------------------------
- # END 'MAIN'
- # ---------------------------------------------------------------------------
-}
-1;
-
-__END__
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
- MySQLTuner 2.8.37 - MySQL High Performance Tuning Script
-
-=head1 IMPORTANT USAGE GUIDELINES
-
-To run the script with the default options, run the script without arguments
-Allow MySQL server to run for at least 24-48 hours before trusting suggestions
-Some routines may require root level privileges (script will provide warnings)
-You must provide the remote server's total memory when connecting to other servers
-
-=head1 OPTIONS
-
-See C for a full list of available options and their categories.
-
-=head1 VERSION
-
-Version 2.8.37
-=head1 PERLDOC
-
-You can find documentation for this module with the perldoc command.
-
- perldoc mysqltuner
-
-=head2 INTERNALS
-
-L
-
- Internal documentation
-
-=head1 AUTHORS
-
-Major Hayden - major@mhtx.net
-Jean-Marie Renouard - jmrenouard@gmail.com
-
-=head1 CONTRIBUTORS
-
-=over 4
-
-=item *
-
-Matthew Montgomery
-
-=item *
-
-Paul Kehrer
-
-=item *
-
-Dave Burgess
-
-=item *
-
-Jonathan Hinds
-
-=item *
-
-Mike Jackson
-
-=item *
-
-Nils Breunese
-
-=item *
-
-Shawn Ashlee
-
-=item *
-
-Luuk Vosslamber
-
-=item *
-
-Ville Skytta
-
-=item *
-
-Trent Hornibrook
-
-=item *
-
-Jason Gill
-
-=item *
-
-Mark Imbriaco
-
-=item *
-
-Greg Eden
-
-=item *
-
-Aubin Galinotti
-
-=item *
-
-Giovanni Bechis
-
-=item *
-
-Bill Bradford
-
-=item *
-
-Ryan Novosielski
-
-=item *
-
-Michael Scheidell
-
-=item *
-
-Blair Christensen
-
-=item *
-
-Hans du Plooy
-
-=item *
-
-Victor Trac
-
-=item *
-
-Everett Barnes
-
-=item *
-
-Tom Krouper
-
-=item *
-
-Gary Barrueto
-
-=item *
-
-Simon Greenaway
-
-=item *
-
-Adam Stein
-
-=item *
-
-Isart Montane
-
-=item *
-
-Baptiste M.
-
-=item *
-
-Cole Turner
-
-=item *
-
-Major Hayden
-
-=item *
-
-Joe Ashcraft
-
-=item *
-
-Jean-Marie Renouard
-
-=item *
-
-Stephan GroBberndt
-
-=item *
-
-Christian Loos
-
-=item *
-
-Long Radix
-
-=back
-
-=head1 SUPPORT
-
-
-Bug reports, feature requests, and downloads at http://mysqltuner.pl/
-
-Bug tracker can be found at https://github.com/jmrenouard/MySQLTuner-perl/issues
-
-Maintained by Jean-Marie Renouard (jmrenouard\@gmail.com) - Licensed under GPL
-
-=head1 SOURCE CODE
-
-L
-
- git clone https://github.com/jmrenouard/MySQLTuner-perl/.git
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright (C) 2006-2026 Major Hayden - major@mhtx.net
-# Copyright (C) 2015-2026 Jean-Marie Renouard - jmrenouard@gmail.com
-
-For the latest updates, please visit http://mysqltuner.pl/
-
-Git repository available at https://github.com/jmrenouard/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 .
-
-=cut
-
-# Local variables:
-# indent-tabs-mode: t
-# cperl-indent-level: 8
-# perl-indent-level: 8
-# End:
diff --git a/mysqltuner.pl.tidy b/mysqltuner.pl.tidy
deleted file mode 100644
index e69de29bb..000000000