#!@@PERL@@ -w
# -*- perl -*-

=head1 NAME

mysql_isam_space_ - Wildcard plugin to monitor the percent of table space used
on isam and myisam tables on a mysql server.

=head1 CONFIGURATION

Configuration parameters for @@CONFDIR@@/PLUGIN,
if you need to override the defaults below:

 [mysql_isam_space_*]
  env.mysqlopts    - Options to pass to mysql
  env.statefile    - where to put the statefile
  env.ignore       - Tables to ignore. Regex.
  env.absolute     - use tables sizes, not percent of maximum

=head2 DEFAULT CONFIGURATION

 [mysql_isam_space_*]
  env.mysqlopts
  env.statefile $ENV{MUNIN_PLUGSTATE}/plugin-mysql_isam_space.state
  env.ignore
  env.absolute 0

=head1 AUTHOR

Unknown author

=head1 LICENSE

GPLv2

=head1 MAGIC MARKERS

=begin comment

These magic markers are used by munin-node-configure when installing
munin-node.

=end comment

 #%# family=contrib
 #%# capabilities=suggest autoconf

=cut


my $DB = `basename $0 | sed 's/^mysql_isam_space_//g' | tr '_' '-'` ;
chomp $DB;
my $STATEFILE = $ENV{'statefile'} || "$ENV{MUNIN_PLUGSTATE}/plugin-mysql_isam_space.state";
my $MYSQLSHOW = $ENV{'mysqlshow'} || `command -v mysqlshow`;
my $ABSOLUTE  = $ENV{'absolute'} || 0;
my @mysql_opts = ();

chomp $MYSQLSHOW;

if (exists $ENV{'mysqlopts'})
{
	@mysql_opts = split /\s+/, $ENV{'mysqlopts'};
}

my $tables = undef;
my $databases = undef;

&print_autoconf () if ($ARGV[0] and $ARGV[0] eq "autoconf");

if (&use_statefile)
{
	($databases, $tables) = &read_statefile;
}
else
{
	($databases, $tables) = &fetch_state_from_mysql;
}

&print_config  ($tables) if ($ARGV[0] and $ARGV[0] eq "config");

&print_suggest ($databases, $tables) if ($ARGV[0] and $ARGV[0] eq "suggest");

foreach my $t (keys %{$tables})
{
	printf ("%s.%s %f\n",  $t, "value ", $tables->{$t});
}

sub print_config
{
	my $tables = shift;

	print "graph_title MySQL \"$DB\" isam/myisam table-space usage\n";
	print "graph_args --base 1000\n";
	print "graph_vlabel percent\n";
	print "graph_category mysql\n";
	foreach my $t (keys %{$tables})
	{
	    	my $label = $t;
		if (length ($t) >= 20)
		{
		    $label = sprintf ("...%17s", substr ($t, -17));
		}
		print $t, ".label $label\n";
		print $t, ".warning 0:90\n";
		print $t, ".critical 0:95\n";
	}
	exit 0;
}

sub read_statefile
{
	my $tables = shift;
	my $databases = undef;

	# Read the statefile
	open (IN, $STATEFILE) or die "Could not open \"$STATEFILE\" for reading: $!";

	while (<IN>)
	{
		tr/זרו/eoa/;
		if (/^$DB\.([^\s]+)\s+([\d\.]+)\s*$/)
		{
			$tables->{$1} = $2;
		}

		if (/^([^\.]+)\./)
		{
			if (! grep (/^$1$/, @{$databases}))
			{
				push (@{$databases}, $1);
			}
		}
	}

	close (IN);
	return ($databases, $tables);
}

sub use_statefile
{
	if (-f $STATEFILE)
	{
		if ((stat ($STATEFILE))[9] > (time - 3600))
		{
			return 1;
		}
	}
	return 0;
}

sub fetch_state_from_mysql
{
	my $tables = shift;

	my $databases = ();

	# Fork off mysqlshow
	my $pid = open (IN, '-|');

	if (! defined ($pid))
	{ # Fork fail
		die "Couldn't fork.";
	}
	elsif ($pid == 0)
	{ # Child
		exec ($MYSQLSHOW, @mysql_opts, "--status", "-v");
	}

	my @mysql_status = <IN>;
	close (IN);
	if ($? != 0)
	{
		exit 1;
	}


	foreach $line (@mysql_status)
	{
		$line =~ tr/זרו/eoa/;
		if ($line =~ /^\|\s+([^\|\s]+)\s+\|\s+([^\|\s]+)\s+\|/)
		{
			next if ($1 eq "Databases");
			next if ($2 eq "N/A");

			if ($2 > 0)
			{
				push (@{$databases}, $1)
			}
		}
	}

	if ((! -e $STATEFILE) or (-f $STATEFILE))
	{
		&makedirfor ($STATEFILE);
		open (OUT, ">$STATEFILE") or die "Could not open \"$STATEFILE\" for writing: $!";
	}
	else
	{
		die "Outfile exists and isn't a \"file\".";
	}


	foreach my $db (@{$databases})
	{
		# Fork off mysqlshow
		my $pid = open (IN, '-|');

		if (! defined ($pid))
		{ # Fork fail
			die "Couldn't fork.";
		}
		elsif ($pid == 0)
		{ # Child
			exec ($MYSQLSHOW, @mysql_opts, "--status", "-v", $db);
			# Notreached.
		}

		my %index;
                my $headerseen;

		while (<IN>)
		{
			my @fields = split (/\s*\|\s*/);
                        next if @fields < 2; # Separator line
			if (! $headerseen and $fields[1] eq "Name")
                        { # Header line, grab field names
                        	%index = map {($fields[$_], $_)} 0..$#fields;
			}
                        else
			{
				# mysqlshow says many fun things sometimes, sanity-check
				next if $fields[$index{Data_length}] eq "";
				next if ($fields[$index{Max_data_length}] eq "") && ( ! $ABSOLUTE );
				next if ($fields[$index{Max_data_length}] == 0) && ( ! $ABSOLUTE );

				my $value =
					$ABSOLUTE ? $fields[$index{Data_length}]
					: (100*$fields[$index{Data_length}]/$fields[$index{Max_data_length}]);
				printf OUT ("%s.%s %f\n", $db, $fields[1], $value);
				$tables->{$fields[1]} = $value if $DB eq $db;
			}
		}
		close (IN);
	}

	close (OUT);

	return ($databases, $tables);
}

sub makedirfor
{
	my $filename = shift;

	(my $dir = $filename) =~ s/\/[^\/]+$//;

	if ($dir ne $filename and ! -d $dir)
	{
		&makedirfor ($dir);
		mkdir ($dir, 0700);
	}
	return $dir;
}

sub print_suggest
{
	my ($databases, $tables) = @_;

	foreach my $db (@{$databases})
	{
		print "$db\n";
	}

	exit 0;
}

sub print_autoconf
{

	# First check that mysqlshow exists...
	if (! -x $MYSQLSHOW)
	{
		print "no (mysqlshow not found)\n";
		exit 0;
	}

	# Fork off mysqlshow
	my $pid = open (IN, '-|');

	if (! defined ($pid))
	{ # Fork fail
		die "Couldn't fork.";
	}
	elsif ($pid == 0)
	{ # Child
		close STDERR;
		open (STDERR, ">&STDOUT");
		exec ($MYSQLSHOW, @mysql_opts, "--status", "-v");
	}

	my @slurp = <IN>;

	close (IN);

	if ($? == 0)
	{
		print "yes\n";
		exit 0;
	}
	elsif (grep (/Can't connect to local MySQL server/, @slurp))
	{
		print "no (could not connect to mysql)\n";
	}
	else
	{
		print "no\n";
	}
	exit 0;
}

# vim:syntax=perl
