#!/usr/bin/perl

# Copyright (c) 2003 Nicholas Barr
#
# This is free software. There is NO warranty; not even for MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.

use strict;

my %settings = ();

$settings{"path_uname"}  = "/bin/uname";
$settings{"path_uptime"} = "/usr/bin/uptie";
$settings{"path_pgsql"}  = "/var/lib/pgsql";

$settings{"pgsql_pagesize_kbytes"} = 8;
$settings{"pgsql_pagesize_bytes"} = $settings{"pgsql_pagesize_kbytes"} * 1024;

######################
# RECOMMENDED RATIOS #
######################

# ratio_shared_buffer
#
# The ratio of shared buffers compared to total memory
# Range : 0 - 1
$settings{"ratio_shared_buffer"}   = 0.0625;	# i.e. 1/16th of total memory

# ratio_effective_cache_size
#
# The ratio of the effective cache size comapared to
# kernel cache size. This should be done on a box that
# has been up quite a while
# Range : 0 - 1
$settings{"ratio_effective_cache_size"} = 0.8;

# Initialise the operating system parameters
my $os_type = "";	# Type of operating system (i.e. Linux/Solaris etc.)
my $os_vers = "";	# Version of the kernel if we actually care?

# Iniitialise the various memory parameters
my $mem_total   = 0;
my $mem_used    = 0;
my $mem_free    = 0;
my $mem_shared  = 0;
my $mem_buffers = 0;
my $mem_cached  = 0;

# Initialise the various shared memory parameters
my $shm_max = 0;

my $uptime = 0;

print "PostgreSQL Autoconfigurer Thingy\n";
print "================================\n\n";

($os_type, $os_vers) = getOperatingSystem();
($mem_total, $mem_used, $mem_free, $mem_shared, $mem_buffers, $mem_cached) = getMemory();
($shm_max) = getShmMax();
($uptime) = getUptime();

my $mem_kcache          = int ($mem_buffers + $mem_cached);

my $pgsql_rec_shrdbuf   = int (($mem_total * $settings{"ratio_shared_buffer"}) / $settings{"pgsql_pagesize_bytes"});
my $pgsql_max_shrdbuf   = int ($shm_max / $settings{"pgsql_pagesize_bytes"});

my $pgsql_shrdbuf;

my $pgsql_max_effective = int ($mem_kcache / $settings{"pgsql_pagesize_bytes"});
my $pgsql_rec_effective = int ($pgsql_max_effective * $settings{"ratio_effective_cache_size"});

my $pgsql_effective;

print "O/S : " . $os_type . ", Kernel : " . $os_vers . "\n";
print "Uptime : " . formatTime($uptime) . "\n";

print "Total memory : " . formatSize($mem_total) . ", Max shared memory : " . formatSize($shm_max) . "\n";

print "  RECOMMEND : shared_buffers = " . $pgsql_rec_shrdbuf . "\n";
print "  MAX       : shared_buffers = " . $pgsql_max_shrdbuf . "\n";

$pgsql_shrdbuf = $pgsql_rec_shrdbuf;

if ($pgsql_rec_shrdbuf > $pgsql_max_shrdbuf)
{
	print "  Recomended value exceeds maximum. Will use max for now. You will need to tweak your shmmax parameter if you wish to increase this.\n";
	$pgsql_shrdbuf = $pgsql_max_shrdbuf;
}

print "Kernel cache : " . formatSize($mem_kcache) . "\n";
print "  RECOMMEND : effective_cache_size = " . $pgsql_rec_effective . "\n";
print "  MAX       : effective_cache_size = " . $pgsql_max_effective . "\n";

$pgsql_effective = $pgsql_rec_effective;

print "Looking for postgresql.conf.\n";

$settings{"path_pgsql_full"} = $settings{"path_pgsql"} . "/data/postgresql.conf";

if (-e $settings{"path_pgsql_full"})
{
	print "  Found in " . $settings{"path_pgsql_full"} . ".\n";

	my $curr_shared_buffers       = 0;
	my $curr_effective_cache_size = 0;

	open (READ,"<" . $settings{"path_pgsql_full"});
	while(<READ>)
	{
		if (/^\#*shared_buffers\s*=\s*([0-9]+)/)
		{
			$curr_shared_buffers = $1;
		}
		if (/^\#*effective_cache_size\s*=\s*([0-9]+)/)
		{
			$curr_effective_cache_size = $1;
		}
	}
	close(READ);

	print "  shared_buffers       = " . $curr_shared_buffers;
	if ($curr_shared_buffers != $pgsql_shrdbuf)
	{
		print " *";
	}
	print "\n";
	print "  effective_cache_size = " . $curr_effective_cache_size;
	if ($curr_effective_cache_size != $pgsql_effective)
	{
		print " *";
	}
	print "\n";

	print "Do you wish to change these settings (y or n) ? ";
	my $line = <STDIN>;
	
	$line = trim($line);

	if (($line eq "y") || ($line eq "Y"))
	{
		open(READ, "<" . $settings{"path_pgsql_full"});
		open(WRITE, ">" . $settings{"path_pgsql_full"} . "\.new");

		while(<READ>)
		{
			if (/^\#*shared_buffers\s*=\s*([0-9]+)/)
			{
				print WRITE "shared_buffers = " . $pgsql_shrdbuf . "\n";
			}
			elsif (/^\#*effective_cache_size\s*=\s*([0-9]+)/)
			{
				print WRITE "effective_cache_size = " . $pgsql_effective . "\n";
			}
			else
			{
				print WRITE $_;
			}
		}

		close (WRITE);
		close (READ);
	}
}

sub getOperatingSystem
{
	my $os_type = "";
	my $os_vers = 0;

	open (READOS, $settings{"path_uname"} . " -s |");
	$os_type = trim(<READOS>);
	close(READOS);

	open (READOS, $settings{"path_uname"} . " -r |");
	$os_vers = trim(<READOS>);
	close(READOS);

	return ($os_type, $os_vers);
}

sub getMemory
{
	my $mem_total;
	my $mem_used;
	my $mem_free;
	my $mem_shared;
	my $mem_buffers;
	my $mem_cached;

	my @line_parts;

	if ($os_type eq "Linux")
	{
		open (READMEM, "/proc/meminfo");
		while (<READMEM>)
		{
			@line_parts = split(" ");

			if ($os_type eq "Linux")
			{
				if ($line_parts[0] eq "Mem:")
				{
					($mem_total, $mem_used, $mem_free, $mem_shared, $mem_buffers, $mem_cached) = 
						@line_parts[1..$#line_parts];
				}
			}
		}
		close(READMEM);
	}

	return ($mem_total, $mem_used, $mem_free, $mem_shared, $mem_buffers, $mem_cached);
}

sub getShmMax
{
	my $shm_max = 0;

	if ($os_type eq "Linux")
	{
		open (READOS, "/proc/sys/kernel/shmmax");
		$shm_max = trim(<READOS>);
		close(READOS);
	}

	return $shm_max;
}

sub getUptime
{
	my $uptime = 0;

	if ($os_type eq "Linux")
	{
		open (READUP, "/proc/uptime");
		($uptime) = split(" ", <READUP>);
		close(READUP);
	}

	return $uptime;
}

sub formatSize
{
	my $size = shift;

	my $newsize = $size;
	my $newtext = "bytes";
	my $newprec = 0;

	my $onekay = 1024;

	if ( ($newsize / $onekay) >= 0.5)
	{
		$newprec = 2;

		$newsize /= $onekay;
		$newtext = "kB";
		if ( ($newsize / $onekay) >= 0.5)
		{
			$newsize /= $onekay;
			$newtext = "MB";
			if ( ($newsize / $onekay) >= 0.5)
			{
				$newsize /= $onekay;
				$newtext = "GB";
				if ( ($newsize / $onekay) >= 0.5)
				{
					$newsize /= $onekay;
					$newtext = "TB";
					if ( ($newsize / $onekay) >= 0.5)
					{
						$newsize /= $onekay;
						$newtext = "PB";
						if ( ($newsize / $onekay) >= 0.5)
						{
							$newsize /= $onekay;
							$newtext = "EB";
							if ( ($newsize / $onekay) >= 0.5)
							{
								$newsize /= $onekay;
								$newtext = "ZB";
							}
						}
					}
				}
			}
		}
	}

	return sprintf("%.".$newprec."f %s", $newsize, $newtext);
}

sub formatTime
{
	my $time = shift;

	my $num;
	my $str;

	$num = $time;
	$str = "seconds";

	return $num . " " . $str;
}

sub trim
{
	my $string = shift;

	$string =~ s/^\s*(.*)/$1/ge;
	$string =~ s/(.*)\s*$/$1/ge;

	return $string;
}

