mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
49dff5a4b6
Specifying filters (filter_status, filter_user, etc) in the mytop config previously wouldn't work, because any filter specified here was added to the config hash as a literal string. This change fixes that - if filter_* is defined in the config and matches an existing filter_* key, then run the value through StringOrRegex() and assign to the config hash.
2702 lines
72 KiB
Bash
2702 lines
72 KiB
Bash
#!/usr/bin/env perl
|
|
#
|
|
# $Id: mytop,v 1.99-maria6 2019/10/22 14:53:51 jweisbuch Exp $
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
mytop - display MariaDB/MySQL server performance info like `top'
|
|
|
|
=cut
|
|
|
|
## most of the POD is at the bottom of the file
|
|
|
|
use 5.005;
|
|
use strict;
|
|
use warnings;
|
|
use DBI;
|
|
use Getopt::Long;
|
|
use Socket;
|
|
use List::Util qw(min max);
|
|
use File::Basename;
|
|
|
|
$main::VERSION = "1.99-maria6";
|
|
my $path_for_script = dirname($0);
|
|
|
|
$| = 1;
|
|
$0 = 'mytop';
|
|
|
|
my $WIN = ($^O eq 'MSWin32') ? 1 : 0;
|
|
|
|
## Test for color support.
|
|
|
|
eval { require Term::ANSIColor; };
|
|
|
|
my $HAS_COLOR = $@ ? 0 : 1;
|
|
|
|
$HAS_COLOR = 0 if $WIN;
|
|
|
|
## Test of Time::HiRes support
|
|
|
|
eval { require Time::HiRes };
|
|
|
|
my $HAS_TIME = $@ ? 0 : 1;
|
|
|
|
my $debug = 0;
|
|
|
|
## Try to lower our priority (which, who, pri)
|
|
|
|
setpriority(0,0,10) unless $WIN;
|
|
|
|
## Prototypes
|
|
|
|
sub Clear();
|
|
sub GetData();
|
|
sub GetQPS();
|
|
sub FullQueryInfo($);
|
|
sub Explain($);
|
|
sub PrintTable(@);
|
|
sub PrintHelp();
|
|
sub Sum(@);
|
|
sub commify($);
|
|
sub make_short($);
|
|
sub Hashes($);
|
|
sub Execute($);
|
|
sub StringOrRegex($);
|
|
sub GetInnoDBStatus();
|
|
sub GetCmdSummary();
|
|
sub GetShowVariables();
|
|
sub GetShowStatus();
|
|
sub cmd_s;
|
|
sub cmd_S;
|
|
sub cmd_q;
|
|
|
|
## Default Config Values
|
|
|
|
my %config = (
|
|
batchmode => 0,
|
|
color => 1,
|
|
db => '',
|
|
database => '',
|
|
delay => 5,
|
|
filter_user => qr/.?/,
|
|
filter_db => qr/.?/,
|
|
filter_host => qr/.?/,
|
|
filter_state => qr/.?/,
|
|
header => 1,
|
|
help => 0,
|
|
host => 'localhost',
|
|
idle => 1,
|
|
long => 120,
|
|
long_nums => 0,
|
|
mode => 'top',
|
|
prompt => 0,
|
|
pass => '',
|
|
password => '',
|
|
port => 3306,
|
|
resolve => 0,
|
|
slow => 10, ## slow query time
|
|
socket => '',
|
|
sort => 1, ## default or reverse sort ("s")
|
|
user => 'root',
|
|
fullqueries => 0, ## shows untruncated queries
|
|
usercol_width => 8, ## User column width
|
|
dbcol_width => 9, ## DB column width
|
|
hide_progress => 0 ## hide the "%" column when available
|
|
);
|
|
|
|
my %qcache = (); ## The query cache--used for full query info support.
|
|
my %ucache = (); ## The user cache--used for full killing by user
|
|
my %dbcache = (); ## The db cache. This should be merged at some point.
|
|
my %statcache = (); ## The show status cache for GetShowStatus()
|
|
|
|
my (%STATUS, %OLD_STATUS); # header stuff.
|
|
|
|
my $CLEAR = $WIN ? '': `clear`;
|
|
|
|
## Term::ReadKey values
|
|
|
|
my $RM_RESET = 0;
|
|
my $RM_NOBLKRD = 3; ## using 4 traps Ctrl-C :-(
|
|
|
|
## Add options from .my.cnf first
|
|
|
|
my $my_print_defaults;
|
|
if (!defined($my_print_defaults=my_which("my_print_defaults")))
|
|
{
|
|
print "Warning: Can't find my_print_defaults. Please add it to your PATH!\n";
|
|
exit(1);
|
|
}
|
|
|
|
unshift @ARGV, split "\n", `$my_print_defaults client mytop`;
|
|
|
|
## Read the user's config file, if it exists.
|
|
|
|
my $config = "$ENV{HOME}/.mytop";
|
|
|
|
if (-e $config)
|
|
{
|
|
if (open CFG, "<$config")
|
|
{
|
|
while (<CFG>)
|
|
{
|
|
next if /^\s*($|#)/; ## skip blanks and comments
|
|
|
|
chomp;
|
|
|
|
if (/(\S+)\s*=\s*(.*\S)/)
|
|
{
|
|
my ($k, $v) = ($1, $2);
|
|
if ($k =~ /^filter_/i) {
|
|
$config{lc $k} = StringOrRegex($v) if exists $config{lc $k};
|
|
} else {
|
|
$config{lc $k} = $v if exists $config{lc $k};
|
|
}
|
|
}
|
|
}
|
|
close CFG;
|
|
}
|
|
## map database/password onto db/pass
|
|
## short version gets precedence for historical reasons
|
|
$config{'db'} = $config{'database'} unless $config{'db'};
|
|
$config{'pass'} = $config{'password'} unless $config{'pass'};
|
|
}
|
|
|
|
## Command-line args.
|
|
|
|
use vars qw($opt_foo);
|
|
|
|
Getopt::Long::Configure('no_ignore_case', 'bundling');
|
|
|
|
GetOptions(
|
|
"color!" => \$config{color},
|
|
"user|u=s" => \$config{user},
|
|
"pass|password|p=s" => \$config{pass},
|
|
"database|db|d=s" => \$config{db},
|
|
"host|h=s" => \$config{host},
|
|
"port|P=i" => \$config{port},
|
|
"socket|S=s" => \$config{socket},
|
|
"delay|s=i" => \$config{delay},
|
|
"batch|batchmode|b" => \$config{batchmode},
|
|
"header!" => \$config{header},
|
|
"idle|i!" => \$config{idle},
|
|
"resolve|r!" => \$config{resolve},
|
|
"prompt!" => \$config{prompt},
|
|
"long=i" => \$config{long},
|
|
"long_nums!" => \$config{long_nums},
|
|
"mode|m=s" => \$config{mode},
|
|
"slow=i" => \$config{slow},
|
|
"sort=s" => \$config{sort},
|
|
"fullqueries|L!" => \$config{fullqueries},
|
|
"usercol_width=i" => \$config{usercol_width},
|
|
"dbcol_width=i" => \$config{dbcol_width},
|
|
"hide_progress|a!" => \$config{hide_progress}
|
|
);
|
|
|
|
## User may have put the port with the host.
|
|
|
|
if ($config{host} =~ s/:(\d+)$//)
|
|
{
|
|
$config{port} = $1;
|
|
}
|
|
|
|
## Don't use Term::ReadKey unless running interactively.
|
|
|
|
if (not $config{batchmode})
|
|
{
|
|
require Term::ReadKey;
|
|
Term::ReadKey->import();
|
|
}
|
|
|
|
## User may want to disable color.
|
|
|
|
if ($HAS_COLOR and not $config{color})
|
|
{
|
|
$HAS_COLOR = 0;
|
|
}
|
|
|
|
if ($HAS_COLOR)
|
|
{
|
|
import Term::ANSIColor ':constants';
|
|
}
|
|
else
|
|
{
|
|
*RESET = sub { };
|
|
*YELLOW = sub { };
|
|
*RED = sub { };
|
|
*MAGENTA = sub { };
|
|
*GREEN = sub { };
|
|
*BLUE = sub { };
|
|
*WHITE = sub { };
|
|
*BOLD = sub { };
|
|
}
|
|
|
|
my $RESET = RESET() || '';
|
|
my $YELLOW = YELLOW() || '';
|
|
my $RED = RED() || '';
|
|
my $MAGENTA = MAGENTA() || '';
|
|
my $GREEN = GREEN() || '';
|
|
my $BLUE = BLUE() || '';
|
|
my $WHITE = WHITE() || '';
|
|
my $BOLD = BOLD() || '';
|
|
|
|
## Connect
|
|
|
|
my $dsn;
|
|
|
|
## Socket takes precedence.
|
|
my $prefix= 'mysql';
|
|
if (eval {DBI->install_driver("MariaDB")}) {
|
|
$dsn = "DBI:MariaDB:database=$config{db};mariadb_read_default_group=mytop;";
|
|
$prefix= 'mariadb'
|
|
} else {
|
|
$dsn = "DBI:mysql:database=$config{db};mysql_read_default_group=mytop;";
|
|
}
|
|
|
|
if ($config{socket} and -S $config{socket})
|
|
{
|
|
$dsn .= "${prefix}_socket=$config{socket}";
|
|
}
|
|
else
|
|
{
|
|
$dsn .= "host=$config{host};port=$config{port}";
|
|
}
|
|
|
|
if ($config{prompt})
|
|
{
|
|
print "Password: ";
|
|
ReadMode(2);
|
|
chomp($config{pass} = <STDIN>);
|
|
ReadMode(0);
|
|
print "\n";
|
|
}
|
|
|
|
my $dbh = DBI->connect($dsn, $config{user}, $config{pass},
|
|
{ PrintError => 0 });
|
|
|
|
if (not ref $dbh)
|
|
{
|
|
my $Error = <<EODIE
|
|
Cannot connect to MariaDB/MySQL server. Please check the:
|
|
|
|
* database you specified "$config{db}" (default is "")
|
|
* username you specified "$config{user}" (default is "root")
|
|
* password you specified "$config{pass}" (default is "")
|
|
* hostname you specified "$config{host}" (default is "localhost")
|
|
* port you specified "$config{port}" (default is 3306)
|
|
* socket you specified "$config{socket}" (default is "")
|
|
|
|
The options my be specified on the command-line or in a ~/.mytop or
|
|
~/.my.cnf config file. See the manual (perldoc mytop) for details.
|
|
|
|
Here's the exact error from DBI. It might help you debug:
|
|
|
|
$DBI::errstr
|
|
|
|
EODIE
|
|
;
|
|
|
|
die $Error;
|
|
|
|
}
|
|
|
|
ReadMode($RM_RESET) unless $config{batchmode};
|
|
|
|
## Get static data
|
|
|
|
my $db_version;
|
|
my $db_release;
|
|
my $server = "MySQL";
|
|
my $have_query_cache;
|
|
|
|
my @variables = Hashes("SHOW VARIABLES");
|
|
|
|
foreach (@variables)
|
|
{
|
|
if ($_->{Variable_name} eq "version")
|
|
{
|
|
$db_version = $_->{Value};
|
|
$db_version =~ /^(\d+)/;
|
|
$db_release = $1;
|
|
$server = "MariaDB" if ($db_version =~ /maria/i);
|
|
# Get the version number only
|
|
$db_version = $1 if($db_version =~ m/(.*?)-/);
|
|
next;
|
|
}
|
|
if ($_->{Variable_name} eq "have_query_cache")
|
|
{
|
|
if ($_->{Value} ne 'NO')
|
|
{
|
|
$have_query_cache = 1;
|
|
}
|
|
else
|
|
{
|
|
$have_query_cache = 0;
|
|
}
|
|
next;
|
|
}
|
|
}
|
|
|
|
my ($has_is_processlist, $has_time_ms, $has_progress);
|
|
$has_is_processlist = $has_time_ms = $has_progress = 0;
|
|
|
|
## Check if the server has the INFORMATION_SCHEMA.PROCESSLIST table
|
|
## for backward compatibility
|
|
$has_is_processlist = Execute("SELECT /*mytop*/ 1 FROM INFORMATION_SCHEMA.TABLES
|
|
WHERE TABLE_SCHEMA = 'information_schema' AND
|
|
TABLE_NAME = 'PROCESSLIST';")->rows;
|
|
if ($has_is_processlist == 1)
|
|
{
|
|
## Check if the server has the TIME_MS column on the I_S.PROCESSLIST table
|
|
## If it is the case, it will fetch the query time with decimal precision
|
|
## for queries that has been running for less than 10k seconds
|
|
$has_time_ms = Execute("SELECT /*mytop*/ 1 FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_SCHEMA = 'information_schema' AND
|
|
TABLE_NAME = 'PROCESSLIST' AND
|
|
COLUMN_NAME = 'TIME_MS';")->rows;
|
|
if ($has_time_ms == 1)
|
|
{
|
|
## Check if the server has the STAGE column on the I_S.PROCESSLIST
|
|
## table (MariaDB) to retreive query completion information
|
|
$has_progress = Execute("SELECT /*mytop*/ 1 FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_SCHEMA = 'information_schema' AND
|
|
TABLE_NAME = 'PROCESSLIST' AND
|
|
COLUMN_NAME = 'STAGE';")->rows;
|
|
}
|
|
}
|
|
|
|
#########################################################################
|
|
##
|
|
## The main loop
|
|
##
|
|
#########################################################################
|
|
|
|
ReadMode($RM_NOBLKRD) unless $config{batchmode};
|
|
|
|
while (1)
|
|
{
|
|
my $key;
|
|
|
|
if ($config{mode} eq 'qps')
|
|
{
|
|
GetQPS();
|
|
$key = ReadKey(1);
|
|
|
|
next unless $key;
|
|
|
|
if ($key =~ /t/i)
|
|
{
|
|
$config{mode} = 'top';
|
|
}
|
|
if ($key =~ /q/)
|
|
{
|
|
cmd_q();
|
|
}
|
|
next;
|
|
}
|
|
if ($config{mode} eq 'top')
|
|
{
|
|
GetData();
|
|
last if $config{batchmode};
|
|
$key = ReadKey($config{delay});
|
|
next unless $key;
|
|
}
|
|
elsif ($config{mode} eq 'cmd')
|
|
{
|
|
GetCmdSummary();
|
|
last if $config{batchmode};
|
|
$key = ReadKey($config{delay});
|
|
next unless $key;
|
|
}
|
|
elsif ($config{mode} eq 'innodb')
|
|
{
|
|
GetInnoDBStatus();
|
|
last if $config{batchmode};
|
|
print "InnoDB Status [hit t to exit this mode or q to exit the application]\n";
|
|
$key = ReadKey($config{delay});
|
|
next unless $key;
|
|
}
|
|
elsif ($config{mode} eq 'status')
|
|
{
|
|
GetShowStatus();
|
|
last if $config{batchmode};
|
|
$key = ReadKey($config{delay});
|
|
next unless $key;
|
|
}
|
|
|
|
##
|
|
## keystroke command processing (if we get this far)
|
|
##
|
|
|
|
if ($key eq '!')
|
|
{
|
|
Execute("STOP /*mytop*/ SLAVE;");
|
|
Execute("SET /*mytop*/ GLOBAL sql_slave_skip_counter=1");
|
|
Execute("START /*mytop*/ SLAVE");
|
|
}
|
|
|
|
# t - top
|
|
|
|
if ($key =~ /t/i)
|
|
{
|
|
$config{mode} = 'top';
|
|
}
|
|
|
|
## q - quit
|
|
|
|
if ($key eq 'q')
|
|
{
|
|
cmd_q();
|
|
}
|
|
|
|
if ($key eq 'D')
|
|
{
|
|
require Data::Dumper;
|
|
print Data::Dumper::Dumper([\%config]);
|
|
ReadKey(0);
|
|
}
|
|
|
|
## l - change long running hightling
|
|
|
|
if ($key eq 'l')
|
|
{
|
|
cmd_l();
|
|
next;
|
|
}
|
|
|
|
## m - mode switch to qps
|
|
|
|
if ($key eq 'm')
|
|
{
|
|
$config{mode} = 'qps';
|
|
Clear() unless $config{batchmode};
|
|
print "Queries Per Second [hit t to exit this mode or q to exit the application]\n";
|
|
next;
|
|
}
|
|
|
|
## c - mode switch to command summary
|
|
|
|
if ($key eq 'c')
|
|
{
|
|
$config{mode} = 'cmd';
|
|
Clear() unless $config{batchmode};
|
|
print "Command Summary\n";
|
|
next;
|
|
}
|
|
|
|
## C - change Color on and off
|
|
|
|
if ($key eq 'C')
|
|
{
|
|
if ($HAS_COLOR)
|
|
{
|
|
$HAS_COLOR = 0;
|
|
}
|
|
else
|
|
{
|
|
$HAS_COLOR = 1;
|
|
}
|
|
}
|
|
|
|
## s - seconds of delay
|
|
|
|
if ($key eq 's')
|
|
{
|
|
cmd_s();
|
|
next;
|
|
}
|
|
|
|
if ($key eq 'S')
|
|
{
|
|
cmd_S();
|
|
next;
|
|
}
|
|
|
|
## R - resolve hostnames
|
|
if ($key eq 'R')
|
|
{
|
|
if ($config{resolve})
|
|
{
|
|
$config{resolve} = 0;
|
|
}
|
|
else
|
|
{
|
|
$config{resolve} = 1;
|
|
}
|
|
}
|
|
|
|
## t - username based filter
|
|
|
|
if ($key eq 't')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Which state (blank for all, /.../ for regex): ", RESET();
|
|
$config{filter_state} = StringOrRegex(ReadLine(0));
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## u - username based filter
|
|
|
|
if ($key eq 'u')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Which user (blank for all, /.../ for regex): ", RESET();
|
|
$config{filter_user} = StringOrRegex(ReadLine(0));
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## d - database name based filter
|
|
|
|
if ($key eq 'd')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Which database (blank for all, /.../ for regex): ",
|
|
RESET();
|
|
$config{filter_db} = StringOrRegex(ReadLine(0));
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## h - hostname based filter
|
|
|
|
if ($key eq 'h')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Which hostname (blank for all, /.../ for regex): ",
|
|
RESET();
|
|
$config{filter_host} = StringOrRegex(ReadLine(0));
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## E - Show full Replication Error
|
|
|
|
if ($key eq 'E')
|
|
{
|
|
my ($data) = Hashes('SHOW /*mytop*/ SLAVE STATUS');
|
|
Clear();
|
|
print "Error is: $data->{Last_Error}\n";
|
|
print RED(), "-- paused. press any key to resume --", RESET();
|
|
ReadKey(0);
|
|
next;
|
|
}
|
|
## F - remove all filters
|
|
|
|
if ($key eq 'F')
|
|
{
|
|
$config{filter_host} = qr/.?/;
|
|
$config{filter_db} = qr/.?/;
|
|
$config{filter_user} = qr/.?/;
|
|
$config{filter_state} = qr/.?/;
|
|
print RED(), "-- display unfiltered --", RESET();
|
|
sleep 1;
|
|
next;
|
|
}
|
|
|
|
## p - pause
|
|
|
|
if ($key eq 'p')
|
|
{
|
|
print RED(), "-- paused. press any key to resume --", RESET();
|
|
ReadKey(0);
|
|
next;
|
|
}
|
|
|
|
## i - idle toggle
|
|
|
|
if ($key =~ /i/)
|
|
{
|
|
if ($config{idle})
|
|
{
|
|
$config{idle} = 0;
|
|
$config{sort} = 1;
|
|
print RED(), "-- idle (sleeping) processed filtered --", RESET();
|
|
sleep 1;
|
|
}
|
|
else
|
|
{
|
|
$config{idle} = 1;
|
|
$config{sort} = 0;
|
|
print RED(), "-- idle (sleeping) processed unfiltered --", RESET();
|
|
sleep 1;
|
|
}
|
|
}
|
|
|
|
## I - InnoDB status
|
|
|
|
if ($key =~ 'I')
|
|
{
|
|
$config{mode} = 'innodb';
|
|
Clear() unless $config{batchmode};
|
|
print "InnoDB Status\n";
|
|
next;
|
|
}
|
|
|
|
## o - sort order
|
|
|
|
if ($key =~ /o/)
|
|
{
|
|
if ($config{sort})
|
|
{
|
|
$config{sort} = 0;
|
|
print RED(), "-- sort order reversed --", RESET();
|
|
sleep 1;
|
|
}
|
|
else
|
|
{
|
|
$config{sort} = 1;
|
|
print RED(), "-- sort order reversed --", RESET();
|
|
sleep 1;
|
|
}
|
|
}
|
|
|
|
## ? - help
|
|
|
|
if ($key eq '?')
|
|
{
|
|
Clear();
|
|
PrintHelp();
|
|
ReadKey(0);
|
|
next;
|
|
}
|
|
|
|
## k - kill
|
|
|
|
if ($key eq 'k')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
|
|
print RED(), "Thread id to kill: ", RESET();
|
|
my $id = ReadLine(0);
|
|
|
|
$id =~ s/\s//g;
|
|
|
|
if ($id =~ /^\d+$/)
|
|
{
|
|
Execute("KILL /*mytop*/ $id");
|
|
}
|
|
else
|
|
{
|
|
print RED(), "-- invalid thread id --", RESET();
|
|
sleep 1;
|
|
}
|
|
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## K - kill based on a username
|
|
if ($key =~ /K/)
|
|
{
|
|
ReadMode($RM_RESET);
|
|
|
|
print RED(), "User to kill: ", RESET();
|
|
my $user = ReadLine(0);
|
|
|
|
$user =~ s/\s//g;
|
|
|
|
if ($user =~ /^\S+$/)
|
|
{
|
|
for my $pid (keys %ucache)
|
|
{
|
|
next unless $ucache{$pid} eq $user;
|
|
Execute("KILL /*mytop*/ $pid");
|
|
select(undef, undef, undef, 0.2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print RED(), "-- invalid thread id --", RESET();
|
|
sleep 1;
|
|
}
|
|
|
|
ReadMode($RM_NOBLKRD);
|
|
}
|
|
|
|
## f - full info
|
|
|
|
if ($key =~ /f/)
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Full query for which thread id: ", RESET();
|
|
my $id = ReadLine(0);
|
|
chomp $id;
|
|
FullQueryInfo($id);
|
|
ReadMode($RM_NOBLKRD);
|
|
print RED(), "-- paused. press any key to resume or (e) to explain --",
|
|
RESET();
|
|
my $key = ReadKey(0);
|
|
|
|
if ($key eq 'e')
|
|
{
|
|
Explain($id);
|
|
print RED(), "-- paused. press any key to resume --", RESET();
|
|
ReadKey(0);
|
|
}
|
|
|
|
next;
|
|
}
|
|
|
|
## e - explain
|
|
|
|
if ($key =~ /e/)
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Explain which query (id): ", RESET();
|
|
my $id = ReadLine(0);
|
|
chomp $id;
|
|
Explain($id);
|
|
ReadMode($RM_NOBLKRD);
|
|
print RED(), "-- paused. press any key to resume --", RESET();
|
|
ReadKey(0);
|
|
next;
|
|
}
|
|
|
|
## r - reset status counters
|
|
|
|
if ($key =~ /r/)
|
|
{
|
|
Execute("FLUSH /*mytop*/ STATUS");
|
|
print RED(), "-- counters reset --", RESET();
|
|
sleep 1;
|
|
next;
|
|
}
|
|
|
|
## H - header toggle
|
|
|
|
if ($key eq 'H')
|
|
{
|
|
if ($config{header})
|
|
{
|
|
$config{header} = 0;
|
|
}
|
|
else
|
|
{
|
|
$config{header}++;
|
|
}
|
|
}
|
|
|
|
## # - magic debug key
|
|
|
|
if ($key eq '#')
|
|
{
|
|
$debug = 1;
|
|
}
|
|
|
|
if ($key eq 'V')
|
|
{
|
|
GetShowVariables();
|
|
print RED(), "-- paused. press any key to resume --", RESET();
|
|
ReadKey(0);
|
|
}
|
|
|
|
## M - switch to SHOW STATUS mode
|
|
|
|
if ($key eq 'M')
|
|
{
|
|
$config{mode} = 'status';
|
|
}
|
|
|
|
## L - full queries toggle
|
|
|
|
if ($key eq 'L')
|
|
{
|
|
if ($config{fullqueries})
|
|
{
|
|
$config{fullqueries} = 0;
|
|
print RED(), "-- full queries OFF --", RESET();
|
|
sleep 1;
|
|
}
|
|
else
|
|
{
|
|
$config{fullqueries} = 1;
|
|
print RED(), "-- full queries ON --", RESET();
|
|
sleep 1;
|
|
}
|
|
}
|
|
|
|
## w - change columns width for the "User" and "Database" columns
|
|
|
|
if ($key eq 'w')
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print RED(), "Width for the 'User' column (the actual value is ".
|
|
$config{usercol_width}."): ";
|
|
my $readWidth = ReadLine(0);
|
|
chomp($readWidth);
|
|
if (defined($readWidth) && $readWidth ne "")
|
|
{
|
|
if ($readWidth > 4 && $readWidth < 60)
|
|
{
|
|
$config{usercol_width} = $readWidth;
|
|
}
|
|
else
|
|
{
|
|
print RED(), "-- Invalid value ($readWidth), the previous value".
|
|
"has been kept, press a key to resume --";
|
|
ReadKey(0);
|
|
}
|
|
}
|
|
print RESET(), RED(), "Width for the 'DB' column (the actual value is ".
|
|
$config{dbcol_width}."): ", RESET();
|
|
$readWidth = ReadLine(0);
|
|
chomp($readWidth);
|
|
if (defined($readWidth) && $readWidth ne "")
|
|
{
|
|
if ($readWidth > 2 && $readWidth < 60)
|
|
{
|
|
$config{dbcol_width} = $readWidth
|
|
}
|
|
else
|
|
{
|
|
print RED(), "-- Invalid value ($readWidth), the previous value".
|
|
"has been kept, press a key to resume --", RESET();
|
|
ReadKey(0);
|
|
}
|
|
}
|
|
undef $readWidth;
|
|
ReadMode($RM_NOBLKRD);
|
|
next;
|
|
}
|
|
|
|
## a - progress column toggle (the column is only displayed
|
|
## if progress information are available from the processlist)
|
|
|
|
if ($key eq 'a')
|
|
{
|
|
if ($config{hide_progress})
|
|
{
|
|
$config{hide_progress} = 0;
|
|
print RED(), "-- progress display ON --", RESET();
|
|
sleep 1;
|
|
}
|
|
else
|
|
{
|
|
$config{hide_progress} = 1;
|
|
print RED(), "-- progress display OFF --", RESET();
|
|
sleep 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
ReadMode($RM_RESET) unless $config{batchmode};
|
|
|
|
exit;
|
|
|
|
#######################################################################
|
|
|
|
sub Clear()
|
|
{
|
|
if (not $WIN)
|
|
{
|
|
print "$CLEAR"
|
|
}
|
|
else
|
|
{
|
|
print "\n" x 90; ## dumb hack for now. Anyone know how to
|
|
## clear the screen in dos window on a Win32
|
|
## system??
|
|
}
|
|
}
|
|
|
|
my $last_time;
|
|
|
|
sub GetData()
|
|
{
|
|
## Get terminal info
|
|
my $now_time;
|
|
%qcache = (); ## recycle memory
|
|
%dbcache = ();
|
|
|
|
my ($width, $height, $wpx, $hpx, $lines_left);
|
|
|
|
if (not $config{batchmode})
|
|
{
|
|
($width, $height, $wpx, $hpx) = GetTerminalSize();
|
|
$lines_left = $height - 2;
|
|
}
|
|
else
|
|
{
|
|
$height = 999_999; ## I hope you don't have more than that!
|
|
$lines_left = 999_999;
|
|
$width = 80;
|
|
}
|
|
|
|
##
|
|
## Header stuff.
|
|
##
|
|
if ($config{header})
|
|
{
|
|
my @recs = "";
|
|
if ($db_release > 4)
|
|
{
|
|
@recs = Hashes("SHOW /*mytop*/ GLOBAL STATUS");
|
|
}
|
|
else
|
|
{
|
|
@recs = Hashes("SHOW /*mytop*/ STATUS");
|
|
}
|
|
|
|
## if the server died or we lost connectivity
|
|
if (not @recs)
|
|
{
|
|
ReadMode($RM_RESET);
|
|
exit 1;
|
|
}
|
|
|
|
## get high-res or low-res time
|
|
my ($t_delta);
|
|
|
|
if ($HAS_TIME)
|
|
{
|
|
$now_time = Time::HiRes::gettimeofday();
|
|
}
|
|
else
|
|
{
|
|
$now_time = time;
|
|
}
|
|
|
|
if ($last_time and $last_time != $now_time)
|
|
{
|
|
$t_delta = $now_time - $last_time;
|
|
}
|
|
|
|
%OLD_STATUS = %STATUS;
|
|
# Set some status that may not exist in all versions
|
|
$STATUS{Handler_tmp_write} = 0;
|
|
$STATUS{Handler_tmp_update} = 0;
|
|
$STATUS{Rows_tmp_read} = 0;
|
|
|
|
foreach my $ref (@recs)
|
|
{
|
|
my $key = $ref->{Variable_name};
|
|
my $val = $ref->{Value};
|
|
|
|
$STATUS{$key} = $val;
|
|
}
|
|
|
|
## Compute Key Cache Hit Stats
|
|
|
|
$STATUS{Key_read_requests} ||= 1; ## can't divide by zero next
|
|
|
|
my $cache_hits_percent = (100-($STATUS{Key_reads}/$STATUS{Key_read_requests}) * 100);
|
|
$cache_hits_percent = sprintf("%2.2f",$cache_hits_percent);
|
|
|
|
## Query Cache info for <= Ver. 4.1
|
|
##
|
|
## mysql> show status like 'qcache%';
|
|
## +-------------------------+----------+
|
|
## | Variable_name | Value |
|
|
## +-------------------------+----------+
|
|
## | Qcache_queries_in_cache | 81 |
|
|
## | Qcache_inserts | 4961668 |
|
|
## | Qcache_hits | 1374170 |
|
|
## | Qcache_not_cached | 5656249 |
|
|
## | Qcache_free_memory | 33164800 |
|
|
## | Qcache_free_blocks | 2 |
|
|
## | Qcache_total_blocks | 168 |
|
|
## +-------------------------+----------+
|
|
##
|
|
## Query Cache info for => Ver. 5.0
|
|
##
|
|
## mysql> show status like 'qcache%';
|
|
## +-------------------------+------------+
|
|
## | Variable_name | Value |
|
|
## +-------------------------+------------+
|
|
## | Qcache_free_blocks | 37652 |
|
|
## | Qcache_free_memory | 110289712 |
|
|
## | Qcache_hits | 1460617356 |
|
|
## | Qcache_inserts | 390563495 |
|
|
## | Qcache_lowmem_prunes | 6414172 |
|
|
## | Qcache_not_cached | 93002420 |
|
|
## | Qcache_queries_in_cache | 66558 |
|
|
## | Qcache_total_blocks | 192031 |
|
|
## +-------------------------+------------+
|
|
|
|
my $query_cache_hits = 0;
|
|
my $query_cache_hits_per_sec = 0;
|
|
my $now_query_cache_hits_per_sec = 0;
|
|
|
|
if ($have_query_cache)
|
|
{
|
|
$query_cache_hits = $STATUS{Qcache_hits};
|
|
$query_cache_hits_per_sec = $STATUS{Qcache_hits} / $STATUS{Uptime};
|
|
|
|
if (defined $last_time and $last_time != $now_time)
|
|
{
|
|
my $q_delta = $STATUS{Qcache_hits} - $OLD_STATUS{Qcache_hits};
|
|
$now_query_cache_hits_per_sec = sprintf "%.2f", $q_delta / $t_delta;
|
|
}
|
|
}
|
|
|
|
my $l;
|
|
if (-e "/proc/loadavg")
|
|
{
|
|
## To avoid warnings if the OS is not Linux
|
|
open (my $fh, "<", "/proc/loadavg");
|
|
## Only the first 3 values are interresting
|
|
$l = join(" ", (split /\s+/, <$fh>)[0..2]);
|
|
close $fh;
|
|
}
|
|
|
|
$last_time = $now_time;
|
|
|
|
## Server Uptime in meaningful terms...
|
|
|
|
my $time = $STATUS{Uptime};
|
|
my ($d,$h,$m,$s) = (0, 0, 0, 0);
|
|
|
|
$d += int($time / (60*60*24)); $time -= $d * (60*60*24);
|
|
$h += int($time / (60*60)); $time -= $h * (60*60);
|
|
$m += int($time / (60)); $time -= $m * (60);
|
|
$s += int($time);
|
|
|
|
my $uptime = sprintf("%d+%02d:%02d:%02d", $d, $h, $m, $s);
|
|
|
|
## Queries per second...
|
|
|
|
my $avg_queries_per_sec = sprintf("%.2f", $STATUS{Questions} / $STATUS{Uptime});
|
|
my $num_queries = $STATUS{Questions};
|
|
|
|
my @t = localtime(time);
|
|
|
|
my $current_time = sprintf "[%02d:%02d:%02d]", $t[2], $t[1], $t[0];
|
|
|
|
my $host_width = length("$server $db_version on $config{host}");
|
|
my $up_width = $width - $host_width - 1;
|
|
Clear() unless $config{batchmode};
|
|
print RESET();
|
|
|
|
printf "%-.${host_width}s %${up_width}s\n",
|
|
"$server $db_version on $config{host}",
|
|
defined($l) ? "load ($l) up $uptime $current_time" : "up $uptime $current_time";
|
|
$lines_left--;
|
|
|
|
|
|
printf " Queries: %-7s qps: %4.0f Slow: %7s Se/In/Up/De(%%): %02.0f/%02.0f/%02.0f/%02.0f\n",
|
|
make_short( $STATUS{Questions} ), # q total
|
|
$STATUS{Questions} / $STATUS{Uptime}, # qps, average
|
|
make_short( $STATUS{Slow_queries} ), # slow
|
|
|
|
# hmm. a Qcache hit is really a select and should be counted.
|
|
100 * ($STATUS{Com_select} + ($STATUS{Qcache_hits}||0) ) / $STATUS{Questions},
|
|
100 * ($STATUS{Com_insert} + $STATUS{Com_replace} ) / $STATUS{Questions},
|
|
100 * ($STATUS{Com_update} ) / $STATUS{Questions},
|
|
100 * $STATUS{Com_delete} / $STATUS{Questions};
|
|
|
|
$lines_left--;
|
|
|
|
if ($t_delta)
|
|
{
|
|
my $q_diff = ( $STATUS{Questions} - $OLD_STATUS{Questions} );
|
|
# print("q_diff: $STATUS{Questions} - $OLD_STATUS{Questions} / $t_delta = $q_diff\n");
|
|
|
|
printf(" Sorts: %6.0f qps now: %4.0f Slow qps: %3.1f Threads: %4.0f (%4.0f/%4.0f) %02.0f/%02.0f/%02.0f/%02.0f\n",
|
|
( $STATUS{Sort_rows} - $OLD_STATUS{Sort_rows} ) / $t_delta,
|
|
|
|
( $STATUS{Questions} - $OLD_STATUS{Questions} ) / $t_delta,
|
|
( # slow now (qps)
|
|
($STATUS{Slow_queries} ) ?
|
|
( $STATUS{Slow_queries} - $OLD_STATUS{Slow_queries} ) / $t_delta :
|
|
0
|
|
),
|
|
$STATUS{Threads_connected},
|
|
$STATUS{Threads_running},
|
|
$STATUS{Threads_cached},
|
|
|
|
(100 * ($STATUS{Com_select} - $OLD_STATUS{Com_select} +
|
|
($STATUS{Qcache_hits}||0) - ($OLD_STATUS{Qcache_hits}||0)
|
|
) ) / ($q_diff ),
|
|
(100 * ($STATUS{Com_insert} - $OLD_STATUS{Com_insert} +
|
|
$STATUS{Com_replace} - $OLD_STATUS{Com_replace}
|
|
) ) / ($q_diff ),
|
|
(100 * ($STATUS{Com_update} - $OLD_STATUS{Com_update}) ) / ($q_diff ),
|
|
(100 * ($STATUS{Com_delete} - $OLD_STATUS{Com_delete}) ) / ($q_diff ),
|
|
);
|
|
}
|
|
else
|
|
{
|
|
print "\n";
|
|
}
|
|
$lines_left--;
|
|
|
|
if ($have_query_cache and $STATUS{Com_select} and $query_cache_hits)
|
|
{
|
|
printf(" Cache Hits: %-5s Hits/s: %4.1f Hits now: %5.1f Ratio: ",
|
|
make_short($STATUS{Qcache_hits}), # cache hits
|
|
$STATUS{Qcache_hits} / $STATUS{Uptime}, # hits / sec
|
|
($t_delta) ? ($STATUS{Qcache_hits} - $OLD_STATUS{Qcache_hits}) / $t_delta : 0, # Hits Now
|
|
);
|
|
|
|
my ($Ratio) = 100 * ($STATUS{Qcache_hits}) / ($STATUS{Qcache_hits} + $STATUS{Com_select} );
|
|
if ($HAS_COLOR)
|
|
{
|
|
print YELLOW() if ($Ratio < 80.0);
|
|
print RED() if ($Ratio < 50.0);
|
|
print MAGENTA() if ($Ratio < 20.0);
|
|
}
|
|
printf("%4.1f%% ",$Ratio);
|
|
if ($HAS_COLOR)
|
|
{
|
|
print RESET();
|
|
}
|
|
|
|
print " Ratio now: ";
|
|
my ($Ratio_now) = ($t_delta) ? # ratio now
|
|
100 * ($STATUS{Qcache_hits} - $OLD_STATUS{Qcache_hits} ) /
|
|
( ($STATUS{Com_select} + $STATUS{Qcache_hits} -
|
|
($OLD_STATUS{Qcache_hits} + $OLD_STATUS{Com_select})
|
|
) || 1) : 0;
|
|
if ($HAS_COLOR)
|
|
{
|
|
print GREEN() if ($Ratio_now >= 80.0);
|
|
print YELLOW() if ($Ratio_now < 80.0);
|
|
print RED() if ($Ratio_now < 50.0);
|
|
print MAGENTA() if ($Ratio_now < 20.0);
|
|
}
|
|
printf("%4.1f%% \n",$Ratio_now);
|
|
if ($HAS_COLOR)
|
|
{
|
|
print RESET();
|
|
}
|
|
$lines_left--;
|
|
}
|
|
|
|
if ($t_delta)
|
|
{
|
|
my $rows_read;
|
|
if (defined($STATUS{Rows_read}))
|
|
{
|
|
$rows_read= $STATUS{Rows_read} - $OLD_STATUS{Rows_read};
|
|
}
|
|
else
|
|
{
|
|
$rows_read=
|
|
($STATUS{Handler_read_first} + $STATUS{Handler_read_key} +
|
|
$STATUS{Handler_read_next} + $STATUS{Handler_read_prev} +
|
|
$STATUS{Handler_read_rnd} + $STATUS{Handler_read_rnd_next} -
|
|
$OLD_STATUS{Handler_read_first} - $OLD_STATUS{Handler_read_key} -
|
|
$OLD_STATUS{Handler_read_next} - $OLD_STATUS{Handler_read_prev} -
|
|
$OLD_STATUS{Handler_read_rnd} - $OLD_STATUS{Handler_read_rnd_next});
|
|
}
|
|
printf(" Handler: (R/W/U/D) %5d/%5d/%5d/%5d Tmp: R/W/U: %5d/%5d/%5d\n",
|
|
$rows_read/$t_delta,
|
|
($STATUS{Handler_write} - $OLD_STATUS{Handler_write}) /
|
|
$t_delta,
|
|
($STATUS{Handler_update} - $OLD_STATUS{Handler_update}) /
|
|
$t_delta,
|
|
($STATUS{Handler_delete} - $OLD_STATUS{Handler_delete}) /
|
|
$t_delta,
|
|
($STATUS{Rows_tmp_read} - $OLD_STATUS{Rows_tmp_read}) /
|
|
$t_delta,
|
|
($STATUS{Handler_tmp_write}
|
|
-$OLD_STATUS{Handler_tmp_write})/$t_delta,
|
|
($STATUS{Handler_tmp_update} -
|
|
$OLD_STATUS{Handler_tmp_update})/$t_delta);
|
|
}
|
|
else
|
|
{
|
|
print "\n";
|
|
}
|
|
|
|
$lines_left--;
|
|
|
|
printf(" MyISAM Key Cache Efficiency: %2.1f%% Bps in/out: %5s/%5s ",
|
|
$cache_hits_percent,
|
|
make_short($STATUS{Bytes_received} / $STATUS{Uptime} ),
|
|
make_short($STATUS{Bytes_sent} / $STATUS{Uptime}));
|
|
printf("Now in/out: %5s/%5s",
|
|
make_short(($STATUS{Bytes_received} - $OLD_STATUS{Bytes_received}) / $t_delta ),
|
|
make_short(($STATUS{Bytes_sent} - $OLD_STATUS{Bytes_sent}) / $t_delta ))
|
|
if ($t_delta);
|
|
print "\n";
|
|
|
|
$lines_left--;
|
|
|
|
my ($data) = Hashes('SHOW /*mytop*/ GLOBAL VARIABLES LIKE "read_only"');
|
|
if ($data->{Value} ne "OFF")
|
|
{
|
|
print RED() if ($HAS_COLOR) ;
|
|
print " ReadOnly";
|
|
RESET() if ($HAS_COLOR);
|
|
}
|
|
|
|
($data) = Hashes('SHOW /*mytop*/ SLAVE STATUS');
|
|
if (defined($data->{Master_Host}))
|
|
{
|
|
if (defined($data->{Seconds_Behind_Master}))
|
|
{
|
|
if ($HAS_COLOR)
|
|
{
|
|
print GREEN();
|
|
print YELLOW() if ($data->{Seconds_Behind_Master} > 60);
|
|
print MAGENTA() if ($data->{Seconds_Behind_Master} > 360);
|
|
}
|
|
}
|
|
print " Replication ";
|
|
print "IO:$data->{Slave_IO_Running} ";
|
|
print "SQL:$data->{Slave_SQL_Running} ";
|
|
print RESET() if ($HAS_COLOR);
|
|
|
|
if (defined($data->{Seconds_Behind_Master}))
|
|
{
|
|
if ($HAS_COLOR)
|
|
{
|
|
print GREEN();
|
|
print YELLOW() if ($data->{Seconds_Behind_Master} > 60);
|
|
print MAGENTA() if ($data->{Seconds_Behind_Master} > 360);
|
|
}
|
|
print "Delay: $data->{Seconds_Behind_Master} sec.";
|
|
}
|
|
else
|
|
{
|
|
my $free = $width - 45;
|
|
my $Err = substr $data->{Last_Error},0 ,$free;
|
|
printf(" ERR: %-${free}s", $Err) if ($Err ne "");
|
|
}
|
|
print WHITE() if ($HAS_COLOR);
|
|
print "\n";
|
|
$lines_left--;
|
|
}
|
|
print "\n";
|
|
}
|
|
|
|
if (not $config{batchmode} and not $config{header})
|
|
{
|
|
Clear();
|
|
print RESET();
|
|
}
|
|
|
|
##
|
|
## Threads
|
|
##
|
|
|
|
my $proc_cmd; ## Query used to fetch the processlist
|
|
my $time_format = "6d";
|
|
|
|
if ($has_is_processlist == 1)
|
|
{
|
|
if ($has_time_ms == 1)
|
|
{
|
|
$time_format = "6.6s";
|
|
if ($has_progress == 1)
|
|
{
|
|
## To have a computed value of "Progress" like the
|
|
## "SHOW PROCESSLIST" one, the Progress column of the query
|
|
## must be replaced by :
|
|
## "CASE WHEN Max_Stage < 2 THEN Progress ELSE
|
|
## (Stage-1)/Max_Stage*100+Progress/Max_Stage END AS Progress"
|
|
$proc_cmd = "SELECT /*mytop*/ Id, User, Host, db, Command,
|
|
CASE WHEN TIME > 10000 THEN Time ELSE
|
|
ROUND(TIME_MS/1000, 1) END AS Time,
|
|
State, Info, Progress, Stage, Max_Stage
|
|
FROM INFORMATION_SCHEMA.PROCESSLIST
|
|
WHERE ID != CONNECTION_ID();";
|
|
}
|
|
else
|
|
{
|
|
$proc_cmd = "SELECT /*mytop*/ Id, User, Host, db, Command,
|
|
CASE WHEN TIME > 10000 THEN Time ELSE
|
|
ROUND(TIME_MS/1000, 1) END AS Time,
|
|
State, Info FROM INFORMATION_SCHEMA.PROCESSLIST
|
|
WHERE ID != CONNECTION_ID();";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$proc_cmd = "SELECT /*mytop*/ Id, User, Host, db, Command, Time,
|
|
State, Info FROM INFORMATION_SCHEMA.PROCESSLIST
|
|
WHERE ID != CONNECTION_ID();";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$proc_cmd = "SHOW /*mytop*/ FULL PROCESSLIST;";
|
|
}
|
|
|
|
## Minimal width values for columns with a dynamic width
|
|
if ($config{usercol_width} < 4) { $config{usercol_width} = 4; }
|
|
if ($config{dbcol_width} < 2) { $config{dbcol_width} = 2; }
|
|
|
|
my @sz = (9, $config{usercol_width}, 15, $config{dbcol_width}, 6, 6, 8);
|
|
if ($has_progress == 1 && !$config{hide_progress}) { push @sz, 5; };
|
|
my $used = scalar(@sz) + Sum(@sz);
|
|
undef(@sz);
|
|
|
|
## If the terminal width <= 80, the state column will have a width of 6
|
|
## chars else it will be between 6 and 15 depending on the terminal width
|
|
my $state = $width <= 80 ? 6 : int(min(6+($width-80)/3, 15));
|
|
## $free = The number of chars between the beginning of the "Query"
|
|
## column and the end of the line
|
|
my $free = $width - $used - ($state - 6);
|
|
my $format= "%9s %$config{usercol_width}s %15s %$config{dbcol_width}s %6s ";
|
|
if ($has_progress == 1 && !$config{hide_progress}) { $format .= "%5s "; }
|
|
$format .= "%6s %${state}s %-.${free}s\n";
|
|
|
|
my $format2 = "%9d %$config{usercol_width}.$config{usercol_width}s %15.15s %$config{dbcol_width}.$config{dbcol_width}s %${time_format} ";
|
|
if ($has_progress == 1 && !$config{hide_progress}) { $format2 .= "%5.1f "; }
|
|
$format2 .= "%6.6s %${state}.${state}s ";
|
|
if ($config{fullqueries})
|
|
{
|
|
$format2 .= "%-${free}s\n";
|
|
}
|
|
else
|
|
{
|
|
$format2 .= "%-${free}.${free}s\n";
|
|
}
|
|
|
|
print BOLD() if ($HAS_COLOR);
|
|
|
|
if ($has_progress == 1 && !$config{hide_progress})
|
|
{
|
|
printf $format,
|
|
'Id','User','Host/IP','DB','Time', '%', 'Cmd', 'State', 'Query';
|
|
}
|
|
else
|
|
{
|
|
printf $format,
|
|
'Id','User','Host/IP','DB','Time', 'Cmd', 'State', 'Query';
|
|
}
|
|
|
|
print RESET() if ($HAS_COLOR);
|
|
|
|
## Id User Host DB
|
|
if ($has_progress == 1 && !$config{hide_progress})
|
|
{
|
|
printf $format,
|
|
'--','----','-------','--','----', '-', '---', '-----', '----------';
|
|
}
|
|
else
|
|
{
|
|
printf $format,
|
|
'--','----','-------','--','----', '---', '-----', '----------';
|
|
}
|
|
|
|
$lines_left -= 2;
|
|
|
|
my @data = Hashes($proc_cmd);
|
|
|
|
foreach my $thread (@data)
|
|
{
|
|
last if not $lines_left;
|
|
|
|
## Drop Domain Name, unless it looks like an IP address. If
|
|
## it's an IP, we'll strip the port number because it's rarely
|
|
## interesting.
|
|
|
|
my $is_ip = 0;
|
|
|
|
if ($thread->{Host} =~ /^(\d{1,3}\.){3}(\d{1,3})(:\d+)?$/)
|
|
{
|
|
$thread->{Host} =~ s/:.*$//;
|
|
$is_ip = 1;
|
|
}
|
|
else
|
|
{
|
|
$thread->{Host} =~ s/^([^.]+).*/$1/;
|
|
}
|
|
|
|
## Otherwise, look up the IP (if resolve is set) and strip the
|
|
## name
|
|
if ($is_ip and $config{resolve})
|
|
{
|
|
$thread->{Host} =~ s/:\d+$//;
|
|
my $host = gethostbyaddr(inet_aton($thread->{Host}), AF_INET);
|
|
if ($host)
|
|
{
|
|
## Only the hostname part of the DNS is kept
|
|
$host =~ s/^([^.]+).*/$1/;
|
|
}
|
|
$thread->{Host} = $host;
|
|
}
|
|
|
|
## Fix possible undefs
|
|
|
|
$thread->{db} ||= '';
|
|
$thread->{Info} ||= '';
|
|
$thread->{Time} ||= 0 ;
|
|
$thread->{Id} ||= 0 ;
|
|
$thread->{User} ||= '';
|
|
$thread->{Command} ||= '';
|
|
$thread->{Host} ||= '';
|
|
$thread->{State} ||= "";
|
|
$thread->{Progress} ||= 0;
|
|
|
|
## Alter double hyphen comments so they don't break
|
|
## the query when newlines are removed - http://freshmeat.net/users/jerjones
|
|
$thread->{Info} =~ s~\s--(.*)$~ /* $1 */ ~mg;
|
|
|
|
## Normalize spaces -- mostly disabled for now. This can
|
|
## break EXPLAIN if you try to explain a mangled query. It
|
|
## may be re-enabled later as an option.
|
|
|
|
## Replace newlines and carriage returns with a space
|
|
$thread->{Info} =~ tr/\n\r/ /;
|
|
|
|
## Leading space removal
|
|
$thread->{Info} =~ s/^\s*//;
|
|
|
|
## Strip non printing control symbols
|
|
$thread->{Info} =~ tr/[[:cntrl:]]//;
|
|
|
|
## Collpase whitespace
|
|
$thread->{Info} =~ s/\s+/ /g;
|
|
|
|
## Trailing space removal
|
|
$thread->{Info} =~ s/\s$//;
|
|
|
|
## Put the first letter of the query uppercase for a better readability
|
|
## with long State strings
|
|
$thread->{Info} = ucfirst $thread->{Info};
|
|
|
|
## Stow it in the cache
|
|
$qcache{$thread->{Id}} = $thread->{Info};
|
|
$dbcache{$thread->{Id}} = $thread->{db};
|
|
$ucache{$thread->{Id}} = $thread->{User};
|
|
|
|
## If Progress information is available and a multi-stage query is
|
|
## running, the actual stage and the total number of stages of the
|
|
## thread are shown at the beginning of the State column
|
|
if ($has_progress == 1 && $thread->{Max_Stage} && $thread->{Max_Stage} > 1)
|
|
{
|
|
$thread->{State} = $thread->{Stage}."/".
|
|
$thread->{Max_Stage}." ".$thread->{State};
|
|
}
|
|
}
|
|
|
|
## Sort by idle time (closest thing to CPU usage I can think of).
|
|
|
|
my @sorted;
|
|
|
|
if (not $config{sort})
|
|
{
|
|
@sorted = sort { $a->{Time} <=> $b->{Time} } @data
|
|
}
|
|
else
|
|
{
|
|
@sorted = sort { $b->{Time} <=> $a->{Time} } @data
|
|
}
|
|
|
|
foreach my $thread (@sorted)
|
|
{
|
|
## Check to see if we can skip out.
|
|
## We skip out if we know the given line doesn't match.
|
|
|
|
next if (($thread->{Command} eq "Sleep")
|
|
and
|
|
(not $config{idle}));
|
|
|
|
next if (($thread->{Command} eq "Binlog Dump")
|
|
and
|
|
(not $config{idle}));
|
|
|
|
next if (($thread->{Command} eq "Daemon")
|
|
and
|
|
(not $config{idle}));
|
|
|
|
next if ($thread->{User} !~ $config{filter_user});
|
|
next if ($thread->{db} !~ $config{filter_db});
|
|
next if ($thread->{Host} !~ $config{filter_host});
|
|
next if ($thread->{State} !~ $config{filter_state});
|
|
|
|
## Otherwise, print.
|
|
|
|
my $smInfo;
|
|
|
|
if ($thread->{Info})
|
|
{
|
|
if ($config{fullqueries})
|
|
{
|
|
$smInfo = $thread->{Info};
|
|
if (length($smInfo) > $free)
|
|
{
|
|
$lines_left -= int((length($smInfo) - $free)/$width) + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$smInfo = substr $thread->{Info}, 0, $free;
|
|
}
|
|
}
|
|
# if ($thread->{State})
|
|
# {
|
|
# $smInfo = substr $thread->{State}, 0, $free;
|
|
# }
|
|
else
|
|
{
|
|
$smInfo = "";
|
|
}
|
|
|
|
$lines_left--;
|
|
if ($lines_left < 0)
|
|
{
|
|
print WHITE(), "-- Truncated query list -- ";
|
|
last;
|
|
}
|
|
|
|
if ($HAS_COLOR)
|
|
{
|
|
print YELLOW() if $thread->{Command} eq 'Query';
|
|
print WHITE() if $thread->{Command} eq 'Sleep';
|
|
print GREEN() if $thread->{Command} eq 'Connect';
|
|
print BOLD() if $thread->{Time} > $config{slow};
|
|
print MAGENTA() if $thread->{Time} > $config{long};
|
|
}
|
|
|
|
if ($has_progress == 1 && !$config{hide_progress})
|
|
{
|
|
printf $format2,
|
|
$thread->{Id}, $thread->{User}, $thread->{Host}, $thread->{db},
|
|
$thread->{Time}, $thread->{Progress}, $thread->{Command},
|
|
$thread->{State}, $smInfo;
|
|
}
|
|
else
|
|
{
|
|
printf $format2,
|
|
$thread->{Id}, $thread->{User}, $thread->{Host}, $thread->{db},
|
|
$thread->{Time}, $thread->{Command}, $thread->{State}, $smInfo;
|
|
}
|
|
|
|
print RESET() if $HAS_COLOR;
|
|
}
|
|
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
my $questions;
|
|
|
|
sub GetQPS()
|
|
{
|
|
my ($data) = Hashes('SHOW /*mytop*/ STATUS LIKE "Questions"');
|
|
my $num = $data->{Value};
|
|
|
|
if (not defined $questions) ## first time?
|
|
{
|
|
$questions = $num;
|
|
return;
|
|
}
|
|
|
|
my $qps = $num - $questions;
|
|
$questions = $num;
|
|
print "$qps\n";
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub GetInnoDBStatus()
|
|
{
|
|
if (not $config{pager})
|
|
{
|
|
if (not $config{pager} = my_which('less'))
|
|
{
|
|
$config{pager} = my_which('more');
|
|
}
|
|
}
|
|
|
|
my @data = Hashes("SHOW /*mytop*/ ENGINE INNODB STATUS");
|
|
|
|
open P, "|$config{pager}" or die "$!";
|
|
print keys %{$data[0]};
|
|
print $data[0]->{Status},"\n";
|
|
close P;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
my %prev_data;
|
|
|
|
sub GetCmdSummary()
|
|
{
|
|
my ($width, $height, $wpx, $hpx, $lines_left);
|
|
|
|
if (not $config{batchmode})
|
|
{
|
|
($width, $height, $wpx, $hpx) = GetTerminalSize();
|
|
|
|
$lines_left = $height - 2;
|
|
}
|
|
else
|
|
{
|
|
$height = 999_999; ## I hope you don't have more than that!
|
|
$lines_left = 999_999;
|
|
$width = 80;
|
|
}
|
|
|
|
## Variable_name and Value pairs come back...
|
|
my @data = Hashes("SHOW /*mytop*/ STATUS LIKE 'Com\\_%'");
|
|
my %cmd_data;
|
|
my %cmd_delta;
|
|
my %cmd_pct;
|
|
my %cmd_delta_pct;
|
|
my $total;
|
|
my $delta_total;
|
|
|
|
for my $item (@data)
|
|
{
|
|
next unless $item->{Value};
|
|
$item->{Variable_name} =~ s/^Com_//;
|
|
$item->{Variable_name} =~ s/_/ /g;
|
|
$cmd_data{$item->{Variable_name}} = $item->{Value};
|
|
$total += $item->{Value};
|
|
}
|
|
|
|
## Populate other stats
|
|
|
|
for my $item (keys %cmd_data)
|
|
{
|
|
$cmd_delta{$item} = $cmd_data{$item} -
|
|
($prev_data{$item} || $cmd_data{$item} - 1);
|
|
|
|
$delta_total += $cmd_delta{$item};
|
|
|
|
$cmd_pct{$item} = int(($cmd_data{$item} / $total) * 100);
|
|
}
|
|
|
|
for my $item (keys %cmd_data)
|
|
{
|
|
$cmd_delta_pct{$item} = int(($cmd_delta{$item} / $delta_total) * 100);
|
|
}
|
|
|
|
|
|
## Display
|
|
|
|
Clear() unless $config{batchmode};
|
|
print RESET();
|
|
printf "%18s %10s %4s | %5s %4s\n", 'Command', 'Total', 'Pct', 'Last', 'Pct';
|
|
printf "%18s %10s %4s | %5s %4s\n", '-------', '-----', '---', '----', '---';
|
|
$lines_left -= 2;
|
|
|
|
for my $item (sort { $cmd_data{$b} <=> $cmd_data{$a} } keys %cmd_data)
|
|
{
|
|
printf "%18s %10d %4s | %5d %4s\n",
|
|
$item,
|
|
$cmd_data{$item},
|
|
$cmd_pct{$item} . "%",
|
|
$cmd_delta{$item},
|
|
$cmd_delta_pct{$item} . "%";
|
|
|
|
last if not $lines_left;
|
|
$lines_left -= 1;
|
|
}
|
|
|
|
%prev_data = %cmd_data;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub GetShowVariables()
|
|
{
|
|
if (not $config{pager})
|
|
{
|
|
if (not $config{pager} = my_which('less'))
|
|
{
|
|
$config{pager} = my_which('more');
|
|
}
|
|
}
|
|
|
|
my @rows = Hashes("SHOW /*mytop*/ VARIABLES");
|
|
|
|
open P, "|$config{pager}" or die "$!";
|
|
|
|
for my $row (@rows)
|
|
{
|
|
my $name = $row->{Variable_name};
|
|
my $value = $row->{Value};
|
|
printf P "%32s: %s\n", $name, $value;
|
|
}
|
|
|
|
close P;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub GetShowStatus()
|
|
{
|
|
Clear() unless $config{batchmode};
|
|
my @rows = Hashes("SHOW /*mytop*/ STATUS");
|
|
|
|
printf "%32s %10s %10s Toggle idle with 'i'\n", 'Counter', 'Total', 'Change';
|
|
printf "%32s %10s %10s\n", '-------', '-----', '------';
|
|
|
|
for my $row (@rows)
|
|
{
|
|
my $name = $row->{Variable_name};
|
|
my $value = $row->{Value};
|
|
my $old = $statcache{$name};
|
|
my $delta = 0;
|
|
|
|
next if $name =~ m/^Com_/; ## skip Com_ stats
|
|
next if $value =~ m/^[^0-9]*$/; ## skip non-numeric
|
|
|
|
## TODO: if Qcache is off, we should skip Qcache_ values
|
|
|
|
if ($HAS_COLOR and defined $old and $old =~ /^\d/)
|
|
{
|
|
if ($value > $old)
|
|
{
|
|
print YELLOW();
|
|
$delta = $value - $old;
|
|
}
|
|
elsif ($value < $old)
|
|
{
|
|
print RED();
|
|
$delta = $value - $old;
|
|
}
|
|
|
|
if (not $config{idle} and $value == $old)
|
|
{
|
|
## filter unchanging stats, maybe
|
|
print RESET();
|
|
next;
|
|
}
|
|
}
|
|
|
|
printf "%32s: %10s %10s\n", $name, $value, $delta;
|
|
print RESET() if $HAS_COLOR;
|
|
|
|
$statcache{$name} = $value;
|
|
}
|
|
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub FullQueryInfo($)
|
|
{
|
|
my $id = shift;
|
|
|
|
if (not exists $qcache{$id} or not defined $qcache{$id})
|
|
{
|
|
print "*** Invalid id. ***\n";
|
|
return;
|
|
}
|
|
|
|
my $sql = $qcache{$id};
|
|
print $CLEAR;
|
|
print "Thread $id was executing following query:\n\n";
|
|
print YELLOW(), $sql,"\n\n", RESET();
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub Explain($)
|
|
{
|
|
my $id = shift;
|
|
|
|
if (not exists $qcache{$id} or not defined $qcache{$id})
|
|
{
|
|
print "*** Invalid id. ***\n";
|
|
return;
|
|
}
|
|
|
|
my $sql = $qcache{$id};
|
|
my $db = $dbcache{$id};
|
|
|
|
Execute("USE /*mytop*/ $db");
|
|
my @info = Hashes("EXPLAIN $sql");
|
|
print $CLEAR;
|
|
print "EXPLAIN $sql:\n\n";
|
|
PrintTable(@info);
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub PrintTable(@)
|
|
{
|
|
my $cnt = 1;
|
|
my @cols = qw(table type possible_keys key key_len ref rows Extra);
|
|
|
|
for my $row (@_)
|
|
{
|
|
print "*** row $cnt ***\n";
|
|
for my $key (@cols)
|
|
{
|
|
my $val = $row->{$key} || 'NULL';
|
|
printf "%15s: %s\n", $key, $val;
|
|
}
|
|
$cnt++;
|
|
}
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub StringOrRegex($)
|
|
{
|
|
my $input = shift;
|
|
chomp $input;
|
|
if (defined $input)
|
|
{
|
|
## regex, strip /.../ and use via qr//
|
|
if ($input =~ m{^/} and $input =~ m{/$})
|
|
{
|
|
$input =~ s{^/}{} if $config{filter_user};
|
|
$input =~ s{/$}{} if $config{filter_user};
|
|
$input = qr/$input/;
|
|
}
|
|
|
|
|
|
## reset to match anything
|
|
elsif ($input eq '')
|
|
{
|
|
$input = qr/.*/;
|
|
}
|
|
|
|
## string, build a simple regex
|
|
else
|
|
{
|
|
$input = '^' . $input . '$';
|
|
$input = qr/$input/;
|
|
}
|
|
}
|
|
|
|
## reset to match anything
|
|
else
|
|
{
|
|
$input = qr/.*/;
|
|
}
|
|
return $input;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub cmd_l
|
|
{
|
|
ReadMode($RM_RESET);
|
|
|
|
print RED(), "Seconds for long queries: ", RESET();
|
|
my $secs = ReadLine(0);
|
|
|
|
if ($secs =~ /^\s*(\d+)/)
|
|
{
|
|
$config{long} = $1;
|
|
if ($config{long} < 1)
|
|
{
|
|
$config{long} = 1;
|
|
}
|
|
}
|
|
ReadMode($RM_NOBLKRD);
|
|
}
|
|
|
|
sub cmd_s
|
|
{
|
|
ReadMode($RM_RESET);
|
|
|
|
print RED(), "Seconds of Delay: ", RESET();
|
|
my $secs = ReadLine(0);
|
|
|
|
if ($secs =~ /^\s*(\d+)/)
|
|
{
|
|
$config{delay} = $1;
|
|
if ($config{delay} < 1)
|
|
{
|
|
$config{delay} = 1;
|
|
}
|
|
}
|
|
ReadMode($RM_NOBLKRD);
|
|
}
|
|
|
|
sub cmd_S
|
|
{
|
|
ReadMode($RM_RESET);
|
|
|
|
print RED(), "Seconds for Slow queries: ", RESET();
|
|
my $secs = ReadLine(0);
|
|
|
|
if ($secs =~ /^\s*(\d+)/)
|
|
{
|
|
$config{slow} = $1;
|
|
if ($config{slow} < 1)
|
|
{
|
|
$config{slow} = 1;
|
|
}
|
|
}
|
|
ReadMode($RM_NOBLKRD);
|
|
}
|
|
|
|
sub cmd_q
|
|
{
|
|
ReadMode($RM_RESET);
|
|
print "\n";
|
|
exit;
|
|
}
|
|
|
|
sub trim($)
|
|
{
|
|
my $string = shift;
|
|
$string =~ s/^\s+//;
|
|
$string =~ s/\s+$//;
|
|
return $string;
|
|
}
|
|
|
|
###########################################################################
|
|
|
|
sub PrintHelp()
|
|
{
|
|
my $help = qq[Help for mytop version $main::VERSION by Jeremy D. Zawodny <${YELLOW}Jeremy\@Zawodny.com${RESET}>
|
|
with updates by Mark Grennan <${YELLOW}mark\@grennan.com${RESET}> and Jean Weisbuch <${YELLOW}jean\@phpnet.org${RESET}>
|
|
|
|
? - display this screen
|
|
# - debug mode (toggle)
|
|
c - command summary view (based on Com_* counters)
|
|
C - turn color on and off
|
|
d - show only a specific database
|
|
e - explain the query that a thread is running
|
|
E - display current replication error
|
|
f - show full query info for a given thread
|
|
F - unfilter the display
|
|
h - show only a specifc host's connections
|
|
H - toggle the mytop header
|
|
i - toggle the display of idle (sleeping) threads
|
|
I - show innodb status
|
|
k - kill a thread
|
|
p - pause the display
|
|
l - change long running queries hightlighing
|
|
m - switch [mode] to qps (queries/sec) scrolling view
|
|
M - switch [mode] to status
|
|
o - reverse the sort order (toggle)
|
|
q - quit
|
|
r - reset the status counters (via FLUSH STATUS on your server)
|
|
R - change reverse IP lookup
|
|
s - change the delay between screen updates
|
|
S - change slow query hightlighting
|
|
t - switch to thread view (default)
|
|
u - show only a specific user
|
|
V - show variables
|
|
: - enter a command (not yet implemented)
|
|
! - Skip an error that has stopped replications (at your own risk)
|
|
L - show full queries (do not strip to terminal width)
|
|
w - adjust the User and DB columns width
|
|
a - toggle the progress column
|
|
|
|
Base version from ${GREEN}http://www.mysqlfanboy.com/mytop-3${RESET}
|
|
This version comes as part of the ${GREEN}MariaDB${RESET} distribution.
|
|
];
|
|
|
|
print $help;
|
|
}
|
|
|
|
sub Sum(@)
|
|
{
|
|
my $sum;
|
|
while (my $val = shift @_) { $sum += $val; }
|
|
return $sum;
|
|
}
|
|
|
|
## A useful routine from perlfaq
|
|
|
|
sub commify($)
|
|
{
|
|
local $_ = shift;
|
|
return 0 unless defined $_;
|
|
1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
|
|
return $_;
|
|
}
|
|
|
|
## Compact numeric representation (10,000 -> 10.0k)
|
|
|
|
sub make_short($)
|
|
{
|
|
my $number = shift;
|
|
return commify($number) if $config{long_nums};
|
|
my $n = 0;
|
|
while ($number > 1_025) { $number /= 1024; $n++; };
|
|
return sprintf "%.1f%s", $number, ('','k','M','G', 'T')[$n];
|
|
}
|
|
|
|
|
|
## Run a query and return the records has an array of hashes.
|
|
|
|
sub Hashes($)
|
|
{
|
|
my $sql = shift;
|
|
my @records;
|
|
|
|
if (my $sth = Execute($sql))
|
|
{
|
|
while (my $ref = $sth->fetchrow_hashref)
|
|
{
|
|
print "record\n" if $debug;
|
|
push @records, $ref;
|
|
}
|
|
}
|
|
return @records;
|
|
}
|
|
|
|
## Execute an SQL query and return the statement handle.
|
|
|
|
sub Execute($)
|
|
{
|
|
my $sql = shift;
|
|
|
|
$sql =~ s/\n/ /sg;
|
|
|
|
my $sth = $dbh->prepare($sql);
|
|
|
|
if (not $sth) { ReadMode($RM_RESET); die $DBI::errstr; }
|
|
|
|
my $ReturnCode = $sth->execute;
|
|
|
|
if (not $ReturnCode)
|
|
{
|
|
if ($debug)
|
|
{
|
|
print "query failed\n";
|
|
sleep 10;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
return $sth;
|
|
}
|
|
|
|
####
|
|
#### my_which is used, because we can't assume that every system has the
|
|
#### which command. my_which can take only one argument at a time.
|
|
#### Return values: requested system command with the first found path,
|
|
#### or undefined, if not found.
|
|
####
|
|
|
|
sub my_which
|
|
{
|
|
my ($command) = @_;
|
|
my (@paths, $path);
|
|
|
|
return $command if (-f $command && -x $command);
|
|
|
|
## Check first if this is a source distribution, then if this binary distribution and last in the path
|
|
|
|
push @paths, "./extra";
|
|
push @paths, $path_for_script;
|
|
push @paths, split(':', $ENV{'PATH'});
|
|
|
|
foreach $path (@paths)
|
|
{
|
|
$path .= "/$command";
|
|
return $path if (-f $path && -x $path);
|
|
}
|
|
return undef();
|
|
}
|
|
|
|
=pod
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
B<mytop> [options]
|
|
|
|
=head1 AVAILABILITY
|
|
|
|
Base version from B<http://www.mysqlfanboy.com/mytop-3>.
|
|
|
|
This version comes as part of the B<MariaDB> distribution. See B<https://mariadb.org/>.
|
|
|
|
And older (the original) version B<mytop> is available from
|
|
http://www.mysqlfanboy.com/mytop-3/ it B<might> also be on CPAN as
|
|
well.
|
|
|
|
=head1 REQUIREMENTS
|
|
|
|
In order for B<mytop> to function properly, you must have the
|
|
following:
|
|
|
|
* Perl 5.005 or newer
|
|
* Getopt::Long
|
|
* DBI and DBD::MariaDB or DBD::mysql
|
|
* Term::ReadKey from CPAN
|
|
|
|
Most systems are likely to have all of those installed--except for
|
|
Term::ReadKey. You will need to pick that up from the CPAN. You can
|
|
pick up Term::ReadKey here:
|
|
|
|
http://search.cpan.org/search?dist=TermReadKey
|
|
|
|
And you obviously need access to a MariaDB server with the necessary
|
|
security to run the I<SHOW PROCESSLIST> and I<SHOW STATUS> commands.
|
|
|
|
If you are a Windows user, using ActiveState's Perl, you can use PPM
|
|
(the Perl Package Manager) to install the MariaDB/MySQL and Term::ReadKey
|
|
modules.
|
|
|
|
=head2 Optional Color Support
|
|
|
|
In additon, if you want a color B<mytop> (recommended), install
|
|
Term::ANSIColor from the CPAN:
|
|
|
|
http://search.cpan.org/search?dist=ANSIColor
|
|
|
|
Once you do, B<mytop> will automatically use it. However, color is not
|
|
yet working on Windows. Patches welcome. :-)
|
|
|
|
=head2 Optional Hi-Res Timing
|
|
|
|
If you want B<mytop> to provide more accurate real-time
|
|
queries-per-second statistics, install the Time::HiRes module from
|
|
CPAN. B<mytop> will automatically notice that you have it and use it
|
|
rather than the standard timing mechanism.
|
|
|
|
=head2 Platforms
|
|
|
|
B<mytop> is known to work on:
|
|
|
|
* Linux (2.2.x, 2.4.x)
|
|
* FreeBSD (2.2, 3.x, 4.x)
|
|
* Mac OS X
|
|
* BSDI 4.x
|
|
* Solaris 2.x
|
|
* Windows NT 4.x (ActivePerl)
|
|
|
|
If you find that it works on another platform, please let me
|
|
know. Given that it is all Perl code, I expect it to be rather
|
|
portable to Unix and Unix-like systems. Heck, it I<might> even work on
|
|
Win32 systems.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Help is always welcome in improving this software. Feel free to
|
|
contact the author (see L<"AUTHOR"> below) with bug reports, fixes,
|
|
suggestions, and comments. Additionally L<"BUGS"> will provide a list
|
|
of things this software is not able to do yet.
|
|
|
|
Having said that, here are the details on how it works and what you can
|
|
do with it.
|
|
|
|
=head2 The Basics
|
|
|
|
B<mytop> was inspired by the system monitoring tool B<top>. I
|
|
routinely use B<top> on Linux, FreeBSD, and Solaris. You are likely to
|
|
notice features from each of them here.
|
|
|
|
B<mytop> will connect to a MariaDB server and periodically run the
|
|
I<SHOW PROCESSLIST> and I<SHOW STATUS> commands and attempt to
|
|
summarize the information from them in a useful format.
|
|
|
|
=head2 The Display
|
|
|
|
The B<mytop> display screen is really broken into two parts. The top 4
|
|
lines (header) contain summary information about your MariaDB
|
|
server. For example, you might see something like:
|
|
|
|
MariaDB 10.5.0 on localhost load (3.89 3.86 3.91) up 7+23:56:31 [16:33:01]
|
|
Queries: 353.4M qps: 531 Slow: 4.5k Se/In/Up/De(%): 87/02/02/00
|
|
Sorts: 2390 qps now: 651 Slow qps: 0.0 Threads: 11 ( 1/ 13) 88/01/03/00
|
|
Handler: (R/W/U/D) 82138/ 5884/ 20/ 1 Tmp: R/W/U: 13623/29501/ 79
|
|
MyISAM Key Cache Efficiency: 99.9% Bps in/out: 157.4k/ 2.2M Now in/out: 554.8k/ 2.6M
|
|
|
|
The first line identifies the hostname of the server (localhost) and
|
|
the version of MariaDB it is running. The right hand side shows the
|
|
uptime of the MariaDB server process in days+hours:minutes:seconds
|
|
format (much like FreeBSD's top) as well as the current time.
|
|
|
|
The second line displays the total number of queries the server has
|
|
processed, the average number of queries per second, the number of
|
|
slow queries, and the percentage of Select, Insert, Update, and Delete
|
|
queries.
|
|
|
|
The third real-time values. First is the number of queries per second,
|
|
then the number of slow queries, followed by query precentages (like
|
|
on the previous line).
|
|
|
|
And the fourth line displays key buffer efficiency (how often keys are
|
|
read from the buffer rather than disk) and the number of bytes that
|
|
MariaDB has sent and received, both over all and in the last cycle.
|
|
|
|
You can toggle the header by hitting B<H> when running B<mytop>.
|
|
|
|
The second part of the display lists as many threads as can fit on
|
|
screen. By default they are sorted according to their idle time (least
|
|
idle first). The display looks like:
|
|
|
|
Id User Host Dbase Time Cmd Query or State
|
|
-- ---- ---- ----- ---- --- --------------
|
|
61 jzawodn localhost music 0 Query show processlist
|
|
|
|
As you can see, the thread id, username, host from which the user is
|
|
connecting, database to which the user is connected, number of seconds
|
|
of idle time, the command the thread is executing, and the query info
|
|
are all displayed.
|
|
|
|
Often times the query info is what you are really interested in, so it
|
|
is good to run B<mytop> in an xterm that is wider than the normal 80
|
|
columns if possible.
|
|
|
|
The thread display color-codes the threads if you have installed color
|
|
support. The current color scheme only works well in a window with a
|
|
dark (like black) background. The colors are selected according to the
|
|
C<Command> column of the display:
|
|
|
|
Query - Yellow
|
|
Sleep - White
|
|
Connect - Green
|
|
Slow - Bright
|
|
Long - Magenta
|
|
|
|
Those are purely arbitrary and will be customizable in a future
|
|
release. If they annoy you just start B<mytop> with the B<--nocolor>
|
|
flag or adjust your config file appropriately.
|
|
|
|
=head2 Arguments
|
|
|
|
B<mytop> handles long and short command-line arguments. Not all
|
|
options have both long and short formats, however. The long arguments
|
|
have two dashes `--'. Short arguments only have one '-'.
|
|
|
|
=over
|
|
|
|
=item B<-u> or B<--user> username
|
|
|
|
Username to use when logging in to the MariaDB server. Default: ``B<root>''.
|
|
|
|
=item B<-p> or B<--pass> or B<--password> I<password>
|
|
|
|
Password to use when logging in to the MariaDB server. Default: none.
|
|
|
|
WARNING: This is insecure as the password is visible for anyone.
|
|
See B<--prompt> instead!
|
|
|
|
=item B<-h> or B<--host> I<hostname>[B<:>I<port>]
|
|
|
|
Hostname of the MariaDB server. The hostname may be followed by an
|
|
option port number. Note that the port is specified separate from the
|
|
host when using a config file. Default: ``B<localhost>''.
|
|
|
|
=item B<--port> or B<-P> I<port>
|
|
|
|
If you're running MariaDB on a non-standard port, use this to specify
|
|
the port number. Default: B<3306>.
|
|
|
|
=item B<-s> or B<--delay> I<seconds>
|
|
|
|
How long between display refreshes. Default: B<5>
|
|
|
|
=item B<-d> or B<--db> or B<--database> I<database>
|
|
|
|
Use if you'd like B<mytop> to connect to a specific database by
|
|
default. Default: none.
|
|
|
|
=item B<-b> or B<--batch> or B<--batchmode>
|
|
|
|
In batch mode, mytop runs only once, does not clear the screen, and
|
|
places no limit on the number of lines it will print. This is suitable
|
|
for running periodically (perhaps from B<cron>) to capture the
|
|
information into a file for later viewing. You might use batch mode in
|
|
a CGI script to occasionally display your MariaDB server status on the
|
|
web.
|
|
|
|
Default: unset.
|
|
|
|
=item B<-S> or B<--socket> I</path/to/socket>
|
|
|
|
If you're running B<mytop> on the same host as MariaDB/MySQL, you may wish to
|
|
have it use the MariaDB/MySQL socket directly rather than a standard TCP/IP
|
|
connection. If you do,just specify one.
|
|
|
|
Note that specifying a socket will make B<mytop> ignore any host
|
|
and/or port that you might have specified. If the socket does not
|
|
exist (or the file specified is not a socket), this option will be
|
|
ignored and B<mytop> will use the hostname and port number instead.
|
|
|
|
Default: none.
|
|
|
|
=item B<--header> or B<--noheader>
|
|
|
|
Specify if you want the header to display or not. You can toggle this
|
|
with the B<h> key while B<mytop> is running.
|
|
|
|
Default: header.
|
|
|
|
=item B<--color> or B<--nocolor>
|
|
|
|
Specify if you want a color display. This has no effect if you don't
|
|
have color support available.
|
|
|
|
Default: If you have color support, B<mytop> will try color unless you
|
|
tell it not to.
|
|
|
|
=item B<-i> or B<--idle> or B<--noi> or B<--noidle>
|
|
|
|
Specify if you want idle (sleeping) threads to appear in the list. If
|
|
sleeping threads are omitted, the default sorting order is reversed so
|
|
that the longest running queries appear at the top of the list.
|
|
|
|
Default: idle.
|
|
|
|
=item B<--prompt> or B<--noprompt>
|
|
|
|
Specify if you want to be prompted to type in your database password.
|
|
This provides a little bit more security since it not only prevents
|
|
the password from viewable in a process list, but also doesn't require
|
|
the password to be stored in plain text in your C<~/.mytop> config file.
|
|
You will B<only> be prompted if a password has not been specified in
|
|
your config file or through another command line option.
|
|
|
|
Default: noprompt.
|
|
|
|
=item B<--resolve>
|
|
|
|
If you have skip-resolve set on MariaDB (to keep it from doing a reverse
|
|
DNS lookup on each inbound connection), mytop can replace IP addresses
|
|
with hostnames but toggling this option.
|
|
|
|
Default: noresolve
|
|
|
|
=item B<--long> or B<--nolong>
|
|
|
|
For large numbers print all digits (e.g. 10.000) instead of using a more
|
|
compact approximation (e.g. 10.0k).
|
|
|
|
Default: nolong.
|
|
|
|
=item B<-m> or B<--mode> I<mode>
|
|
|
|
Specify initial mode B<qps>(queries/second), B<top>(overview),
|
|
B<cmd>(command summary), B<innodb>(InnoDB status) or B<status>().
|
|
|
|
Default: B<top>
|
|
|
|
=item B<--sort> or B<--nosort>
|
|
|
|
Reverse sort order from ascending to descending using Idle time.
|
|
|
|
Default: nosort.
|
|
|
|
|
|
=back
|
|
|
|
Command-line arguments will always take precedence over config file
|
|
options. That happens because the config file is read I<BEFORE> the
|
|
command-line arguments are applied.
|
|
|
|
=head2 Config File
|
|
|
|
Instead of always using bulky command-line parameters, you can also
|
|
use a config files for the default value of your options.
|
|
|
|
mytop will first read the [client] and [mytop] sections from your
|
|
my.cnf files. After that it will read the (C<~/.mytop>) file from your
|
|
home directory (if present). These are read I<before> any of your
|
|
command-line arguments are processed, so your command-line arguments
|
|
will override directives in the config file.
|
|
|
|
|
|
Here is a sample config file C<~/.mytop> which implements the defaults
|
|
described above.
|
|
|
|
user=root
|
|
pass=
|
|
host=localhost
|
|
db=test
|
|
delay=5
|
|
port=3306
|
|
slow=10
|
|
socket=
|
|
batchmode=0
|
|
header=1
|
|
color=1
|
|
idle=1
|
|
long=120
|
|
|
|
Using a config file will help to ensure that your database password
|
|
isn't visible to users on the command-line. Just make sure that the
|
|
permissions on C<~/.mytop> are such that others cannot read it (unless
|
|
you want them to, of course).
|
|
|
|
You may have white space on either side of the C<=> in lines of the
|
|
config file.
|
|
|
|
=head2 Shortcut Keys
|
|
|
|
The following keys perform various actions while B<mytop> is
|
|
running. Those which have not been implemented are listed as
|
|
such. They are included to give the user idea of what is coming.
|
|
|
|
=over
|
|
|
|
=item B<?>
|
|
|
|
Display help.
|
|
|
|
=item B<c>
|
|
|
|
Show "command counters" based on the Com_* values in SHOW STATUS.
|
|
This is a new feature. Feedback welcome.
|
|
|
|
=item B<C>
|
|
|
|
Turn display color on and off. Default is on.
|
|
|
|
=item B<d>
|
|
|
|
Show only threads connected to a particular database.
|
|
|
|
=item B<f>
|
|
|
|
Given a thread id, display the entire query that thread was (and still
|
|
may be) running.
|
|
|
|
=item B<F>
|
|
|
|
Disable all filtering (host, user, and db).
|
|
|
|
=item B<h>
|
|
|
|
Only show queries from a particular host.
|
|
|
|
=item B<H>
|
|
|
|
Toggle the header display. You can also specify either C<header=0> or
|
|
C<header=1> in your config file to set the default behavior.
|
|
|
|
=item B<i>
|
|
|
|
Toggle the display of idle (sleeping) threads. If sleeping threads are
|
|
filtered, the default sorting order is reversed so that the longest
|
|
running queries appear at the top of the list.
|
|
|
|
=item B<I>
|
|
|
|
Switch to InnoDB Status mode. The output of "SHOW ENGINE INNODB STATUS" will
|
|
be displayed every cycle. In a future version, this may actually
|
|
summarize that data rather than producing raw output.
|
|
|
|
=item B<k>
|
|
|
|
Kill a thread.
|
|
|
|
=item B<m>
|
|
|
|
Toggle modes. Currently this switches from `top' mode to `qps'
|
|
(Queries Per Second Mode). In this mode, mytop will write out one
|
|
integer per second. The number written reflects the number of queries
|
|
executed by the server in the previous one second interval.
|
|
|
|
More modes may be added in the future.
|
|
|
|
=item B<o>
|
|
|
|
Reverse the default sort order.
|
|
|
|
=item B<p>
|
|
|
|
Pause display.
|
|
|
|
=item B<q>
|
|
|
|
Quit B<mytop>
|
|
|
|
=item B<r>
|
|
|
|
Reset the server's status counters via a I<FLUSH STATUS> command.
|
|
|
|
=item B<R>
|
|
|
|
Togle IP reverse lookup. Default is on.
|
|
|
|
=item B<s>
|
|
|
|
Change the sleep time (number of seconds between display refreshes).
|
|
|
|
=item B<S>
|
|
|
|
Set the number of seconds a query will need to run before it is
|
|
considered old and will be highlighted.
|
|
|
|
=item B<u>
|
|
|
|
Show only threads owned by a giver user.
|
|
|
|
=back
|
|
|
|
The B<s> key has a command-line counterpart: B<-s>.
|
|
|
|
The B<h> key has two command-line counterparts: B<--header> and
|
|
B<--noheader>.
|
|
|
|
=head1 BUGS
|
|
|
|
This is more of a BUGS + WishList.
|
|
|
|
Some performance information is not available when talking to a
|
|
version 3.22.x MySQL server. Additional information (about threads
|
|
mostly) was added to the output of I<SHOW STATUS> in MySQL 3.23.x and
|
|
B<mytop> makes use of it. If the information is not available, you
|
|
will simply see zeros where the real numbers should be.
|
|
|
|
Simply running this program will increase your overall counters (such
|
|
as the number of queries run). But you may or may not view that as a
|
|
bug.
|
|
|
|
B<mytop> consumes too much CPU time when running (verified on older
|
|
versions of Linux and FreeBSD). It's likely a problem related to
|
|
Term::ReadKey. I haven't had time to investigate yet, so B<mytop> now
|
|
automatically lowers its priority when you run it. You may also think
|
|
about running B<mytop> on another workstation instead of your database
|
|
server. However, C<mytop> on Solaris does B<not> have this problem.
|
|
Newer versions of Linux and FreeBSD seem to have fixed this.
|
|
|
|
You can't specify the maximum number of threads to list. If you have
|
|
many threads and a tall xterm, B<mytop> will always try to display as
|
|
many as it can fit.
|
|
|
|
The size of most of the columns in the display has a small maximum
|
|
width. If you have fairly long database/user/host names the display
|
|
may appear odd. I have no good idea as to how best to deal with that
|
|
yet. Suggestions are welcome.
|
|
|
|
It'd be nice if you could just add B<mytop> configuration directives
|
|
in your C<my.cnf> file instead of having a separate config file.
|
|
|
|
You should be able to specify the columns you'd like to see in the
|
|
display and the order in which they appear. If you only have one
|
|
username that connects to your database, it's probably not worth
|
|
having the User column appear, for example.
|
|
|
|
=head1 AUTHOR
|
|
|
|
mytop was developed and is maintained by Jeremy D. Zawodny
|
|
(Jeremy@Zawodny.com).
|
|
|
|
If you wish to e-mail me regarding this software, B<PLEASE> subscribe
|
|
to the B<mytop> mailing list. See the B<mytop> homepage for details.
|
|
|
|
=head1 DISCLAIMER
|
|
|
|
While I use this software in my job at Yahoo!, I am solely responsible
|
|
for it. Yahoo! does not necessarily support this software in any
|
|
way. It is merely a personal idea which happened to be very useful in
|
|
my job.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
Please check the MariaDB manual if you're not sure where some of the
|
|
output of B<mytop> is coming from.
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright (C) 2000-2010, Jeremy D. Zawodny.
|
|
|
|
=head1 CREDITS
|
|
|
|
Fix a bug. Add a feature. See your name here!
|
|
|
|
Many thanks go to these fine folks:
|
|
|
|
=over
|
|
|
|
=item Jean Weisbuch (jean@phpnet.org)
|
|
|
|
Added --fullqueries and --sort options, dynamic user and database columns
|
|
width, reading of .my.cnf, state/progress column that can be disabled
|
|
dynamically (when available) and various small fixes.
|
|
|
|
=item Michael "Monty" Widenius <monty@askmonty.org>
|
|
|
|
Fixed a couple of minor bugs that gave warnings on startup.
|
|
Added support for MariaDB (show MariaDB at top and % done).
|
|
Cut long server version names to display width.
|
|
Made 'State' length dynamic.
|
|
|
|
=item Mark Grennan (mark@grennan.com) www.linuxfangoy.com
|
|
|
|
Added updates for MySQL 5.x. Added 'S' (slow) highlighting.
|
|
Added 'C' to turn on and off Color. Added 'l' command to change
|
|
color for long running queries. Fixed a few documentation issues.
|
|
Monitors Slave status. Added color to Queue hit ratio.
|
|
Added number of rows sorted per second.
|
|
Created release 1.7.
|
|
|
|
=item Sami Ahlroos (sami@avis-net.de)
|
|
|
|
Suggested the idle/noidle stuff.
|
|
|
|
=item Jan Willamowius (jan@janhh.shnet.org)
|
|
|
|
Minor bug report. Documentation fixes.
|
|
|
|
=item Alex Osipov (alex@acky.net)
|
|
|
|
Long command-line options, Unix socket support.
|
|
|
|
=item Stephane Enten (tuf@grolier.fr)
|
|
|
|
Suggested batch mode.
|
|
|
|
=item Richard Ellerbrock (richarde@eskom.co.za)
|
|
|
|
Bug reports and usability suggestions.
|
|
|
|
=item William R. Mattil (wrm@newton.irngtx.tel.gte.com)
|
|
|
|
Bug report about empty passwords not working.
|
|
|
|
=item Benjamin Pflugmann (philemon@spin.de)
|
|
|
|
Suggested -P command-line flag as well as other changes.
|
|
|
|
=item Justin Mecham <justin@aspect.net>
|
|
|
|
Suggested setting $0 to `mytop'.
|
|
|
|
=item Thorsten Kunz <thorsten.kunz@de.tiscali.com>
|
|
|
|
Provided a fix for cases when we try remove the domain name from the
|
|
display even if it is actually an IP address.
|
|
|
|
=item Sasha Pachev <sasha@mysql.com>
|
|
|
|
Provided the idea of real-time queries per second in the main display.
|
|
|
|
=item Paul DuBois <paul@snake.net>
|
|
|
|
Pointed out some option-handling bugs.
|
|
|
|
=item Mike Wexler <mwexler@tias.com>
|
|
|
|
Suggested that we don't mangle (normalize) whitespace in query info by
|
|
default.
|
|
|
|
=item Mark Zweifel <markez@yahoo-inc.com>
|
|
|
|
Make the --idle command-line argument negatable.
|
|
|
|
=item Axel Schwenke <schwenke@jobpilot.de>
|
|
|
|
Noticed the incorrect formula for query cache hit percentages in
|
|
version 1.2.
|
|
|
|
=item Steven Roussey <sroussey@network54.com>
|
|
|
|
Supplied a patch to help filter binary junk in queries so that
|
|
terminals don't freak out.
|
|
|
|
=item Jon R. Luini <falcon@chime.com>
|
|
|
|
Supplied a patch that formed the basis for C<--prompt> support.
|
|
Sean Leach <sleach@wiggum.com> submitted a similar patch.
|
|
|
|
=item Yogish Baliga <baliga@yahoo-inc.com>
|
|
|
|
Supplied a patch that formed the basis for C<--resolve> support.
|
|
|
|
=item Per Andreas Buer <perbu@linpro.no>
|
|
|
|
Supplied an excellent patch to tidy up the top display. This includes
|
|
showing most values in short form, such as 10k rather than 10000.
|
|
|
|
=back
|
|
|
|
See the Changes file on the B<mytop> distribution page for more
|
|
details on what has changed.
|
|
|
|
=head1 LICENSE
|
|
|
|
B<mytop> is licensed under the GNU General Public License version
|
|
2. For the full license information, please visit
|
|
http://www.gnu.org/copyleft/gpl.html
|
|
|
|
=cut
|
|
|
|
__END__
|