#!/usr/bin/perl
# AUTHOR: cbirchinger@netswarm.net
# VERSION: 0.4 (Sep 18 2013)

use strict;
use DBI;
use Getopt::Long qw(:config posix_default no_ignore_case);
use Term::ReadKey;

my @dbcfgs = ( "$ENV{HOME}/.my-root.cnf", "$ENV{HOME}/.my.cnf", "/etc/mysql/my.cnf", "/etc/my.cnf" );
my $dbuser; # leave empty to use the config file
my $dbpass; # leave empty to use the config file
my $dbhost; # leave empty to use the config file

my %ansi = (
	id => "\e[38;5;227m",
	user => "\e[38;5;99m",
	host => "\e[38;5;159m",
	db => "\e[38;5;208m",
	command => "\e[38;5;34m",
	time => "\e[38;5;118m",
	state => "\e[38;5;68m",
	info => "\e[38;5;250m",
	change => "\e[38;5;196m",
	add => "\e[38;5;118m",
	read => "\e[38;5;230m",
	sleep => "\e[38;5;244m",
	reset => "\e[1;0m",
	titlebg => "\e[48;5;238m",
);

my $db='information_schema';
my $dbquery="SELECT ID,USER,HOST,IFNULL(DB, '') as DB,COMMAND,TIME,STATE,IFNULL(INFO, '') as INFO FROM information_schema.processlist ORDER BY ID";

my %opts;
GetOptions(\%opts,
	'host|h=s',
	'user|u=s',
	'password|p=s',
	'config|c=s',
	'nocolor|n',
	'help',
) || printhelp();

printhelp() if ($opts{'help'});

$dbhost = $ARGV[0] if ($ARGV[0]);
$dbhost = $opts{'host'} if ($opts{'host'});
$dbuser = $opts{'user'} if ($opts{'user'});
$dbpass = $opts{'password'} if ($opts{'password'});

my $dbcfg;
if ($opts{'config'}) {
	$dbcfg  = $opts{'config'};
	print STDERR "Warning: Can't open file $dbcfg\n" if (! -r $dbcfg);
} else {
	foreach (@dbcfgs) {
		if (-r $_) {
			$dbcfg = $_;
			last;
		}
	}
}

my $dbsrvstr;
$dbsrvstr = ";host=${dbhost}" if ($dbhost);

my $dbh = DBI->connect (
	"DBI:mysql:database=${db}${dbsrvstr}"
	. ";mysql_read_default_file=${dbcfg}",
	$dbuser, $dbpass,
	{ RaiseError => 0, PrintError => 0 }
) or abort("Couldn't connect to database: $DBI::errstr");

my $sql = $dbh->prepare($dbquery);
$sql->execute;
abort("SQL Error. return code: " . $sql->err . " error msg: " . $sql->errstr) if ($sql->err);

my @labels = qw(id user host db command time state info);

my %max = map { $_ => 15; } @labels;

# Strip colors
if ($opts{'nocolor'}) {
	%max = map { $_ => 3; } @labels;
	%ansi = map { $_ => ''; } @labels;
}

my @result;
my $sleep = 0;

while ( my $ref = $sql->fetchrow_arrayref() ) {
	my %data;
	@data{'id', 'user', 'host', 'db', 'command', 'time', 'state', 'info'} = @$ref;

	next if ( $data{'info'} =~ /information_schema.processlist/ );

	if ( $data{'command'} eq 'Sleep' ) {
		$sleep++;
		next;
	}

	my %colordata;
	foreach (@labels) {
		$colordata{$_} = $ansi{$_} . $data{$_};
	}

	foreach (keys(%colordata)) {
		if ( length($colordata{$_}) > $max{$_} ) {
				$max{$_} = length($colordata{$_})
		}
	}
	push(@result, \%colordata);
}
$sql->finish();

my @colorlabels;
foreach (@labels) {
	push (@colorlabels, "$ansi{$_}" . ucfirst($_));
}

(my $wchar) = GetTerminalSize();
my $ttext = sprintf("$ansi{'titlebg'}%-$max{'id'}s %-$max{'user'}s %-$max{'host'}s %-$max{'db'}s %-$max{'command'}s %-$max{'time'}s %s", @colorlabels);
my $ttpad = $wchar - vislength($ttext);
print ${ttext} . ' ' x $ttpad . $ansi{'reset'} . "\n";

foreach (@result) {
	my %data = %{ $_ };
	printf("%-$max{'id'}s %-$max{'user'}s %-$max{'host'}s %-$max{'db'}s %-$max{'command'}s %-$max{'time'}s %s\n",
		$data{'id'}, $data{'user'}, $data{'host'}, $data{'db'}, $data{'command'}, $data{'time'}, $data{'state'});

	if ( length($data{'info'}) > length($ansi{'info'}) ) {
		$data{'info'} =~ s/^\s*\e?\[[\d;]*m?\s*(select|show)(\s.*)/$ansi{'read'}${1}$ansi{'info'}${2}/i
		|| $data{'info'} =~ s/^\s*\e?\[[\d;]*m?\s*(delete|update|alter|drop|set)(\s.*)/$ansi{'change'}${1}$ansi{'info'}${2}/i
		|| $data{'info'} =~ s/^\s*\e?\[[\d;]*m?\s*(insert|load|create)(\s.*)/$ansi{'add'}${1}$ansi{'info'}${2}/i;
		print "$ansi{'sleep'}↳ $data{'info'}\n";
	}
}

my $ftext = "$ansi{'sleep'}$ansi{'titlebg'} Sleeping: ${sleep}";
my $ftpad = $wchar - vislength($ftext);
print ${ftext} . ' ' x $ftpad . $ansi{'reset'} . "\n";

sub vislength {
	my ($str) = @_;
	$str =~ s/\e\[\d+(?>(;\d+)*)m//g;
	return length($str);
}

sub abort {
	my ($errstr) = @_;
	$errstr = "ERROR: Unknown error\n" if ! $errstr;
	print STDERR $errstr . "\n";
	exit 1;
}

sub printhelp {
	(my $basename = $0) =~ s/.*\///;
	print "Usage: $basename [OPTION]... [HOST]\n";
	print "\n";
	print "        --help                   Display this help screen\n";
	print "        -n, --nocolor            No ANSI color sequences\n";
	print "        -c, --config=CONFIG      Read my.cnf config file. Use [client] section credentials and host\n";
	print "        -u, --user=USER          MySQL user name\n";
	print "        -p, --password=PASSWORD  MySQL password\n";
	print "        -h, --host=HOST          MySQL server host\n";
	print "        HOST                     MySQL server host\n";
	print "\n";
	print "Default options are read from the following files in the given order:\n" . join(' ', @dbcfgs) . "\n";
	print "\n";
	exit 0;
}