#!/usr/bin/perl -w
use POSIX;

open FILE, "/proc/user_beancounters" or die "Cannot open beancounters";
while (<FILE>) {
   next if /^Version/;
   next if /failcnt/;

   @line = split;
   if (scalar @line == 7) {
      $id = shift @line;
      $id =~ s/://;
   }
   $hash{$id}->{$line[0]}->{'held'} = $line[1];
   $hash{$id}->{$line[0]}->{'maxheld'} = $line[2];
   $hash{$id}->{$line[0]}->{'barrier'} = $line[3];
   $hash{$id}->{$line[0]}->{'limit'} = $line[4];
   $hash{$id}->{$line[0]}->{'failcnt'} = $line[5];
}
close FILE;

open FILE, "/proc/meminfo" or die "Cannot open meminfo";
while (<FILE>) {
   @line = split;
   $line[0] =~ s/://;
   next if !defined $line[2];
   if ($line[2] eq 'kB') {
      $line[1] *= 1024;
   }
   $mem{$line[0]} = $line[1];
}
close FILE;


# Barrier of numproc, numtcpsock, numothersock, dgramrcvbuf should be set to the limit:
$URL="http://wiki.openvz.org/UBC_primary_parameters";
foreach $id (keys %hash) {
   next if $id == 0;
   foreach $param ('numproc', 'numtcpsock', 'numothersock', 'dgramrcvbuf') {
      $l = $hash{$id}->{$param}->{'limit'};
      $b = $hash{$id}->{$param}->{'barrier'};
      if ($l != $b) {
         print "VEID $id: Should set $param barrier equal to limit $l (is $b)\n\t$URL\n";
         printValues($id, $param);
      }
   }
}

# Limit of numproc about 16000
$URL="http://wiki.openvz.org/UBC_primary_parameters#numproc";
foreach $id (keys %hash) {
   next if $id == 0;
   $l = $hash{$id}->{'numproc'}->{'limit'};
   if ($l > 16000) {
      print "VEID $id: Should set numproc limit to about 16000 (is $l)\n\t$URL\n";
   }
}

# limit of vmguarpages should be MAX_ULONG
$URL="http://wiki.openvz.org/UBC_primary_parameters#vmguarpages";
foreach $id (keys %hash) {
   next if $id == 0;
   $l = $hash{$id}->{'vmguarpages'}->{'limit'};
   if ($l != LONG_MAX) {
      printOut (
         "VEID $id: Should set vmguarpages limit to ".LONG_MAX
      );
      printValues($id, 'vmguarpages');
   }
}

# kmemsize limit 10% more than barrier
$URL="http://wiki.openvz.org/UBC_secondary_parameters#kmemsize";
foreach $id (keys %hash) {
   next if $id == 0;
   $l = $hash{$id}->{'kmemsize'}->{'limit'};
   $b = $hash{$id}->{'kmemsize'}->{'barrier'};
   if ($l < $b*1.1) {
      print "VEID $id: Should set kmemsize limit to 1.1*barrier (currently $b:$l)\n\t$URL\n";
   }
}

# tcpsndbuf, tcprcvbuf, othersockbuf limits
$URL="http://wiki.openvz.org/UBC_secondary_parameters#tcpsndbuf";
foreach $id (keys %hash) {
   next if $id == 0;
   $map = {
      'tcpsndbuf' => 'numtcpsock',
      'tcprcvbuf' => 'numtcpsock',
      'othersockbuf' => 'numothersock',
   };
   foreach $param (keys %$map) {
      $rhs = $map->{$param};
      $num =  $hash{$id}->{$rhs}->{'barrier'}*2.5*1024;
      $l = $hash{$id}->{$param}->{'limit'};
      $b = $hash{$id}->{$param}->{'barrier'};
      if ($l - $b < $num) {
         printOut(
            "VEID $id: $param limit too low",
            $URL,
            "Formula: ${param}[limit-barrier] > 2.5Kb*$rhs",
            "\t${param}[limit-barrier] = ".niceSize($l-$b),
            "\t        2.5Kb*$rhs = ".niceSize($num),
            "Either raise ${param}[limit] to ".($b+$num),
            "or lower ${param}[barrier]   to ".($l-$num),
            "or lower $rhs to ".($l-$b)/$num
         );
         printValues($id, $param);
         printValues($id, $rhs);
      }
   }
}

# privvmpages limit more than barrier
$URL="http://wiki.openvz.org/UBC_secondary_parameters#privvmpages";
foreach $id (keys %hash) {
   next if $id == 0;
   $l = $hash{$id}->{'privvmpages'}->{'limit'};
   $b = $hash{$id}->{'privvmpages'}->{'barrier'};
   if ($l <= $b) {
      print "VEID $id: Should set privvmpages limit to more than barrier (currently $b:$l)\n\t$URL\n";
   }
}

# kmemsize large enough for all processes
$URL="http://wiki.openvz.org/UBC_consistency_check#kmemsize_should_be_enough_for_the_expected_number_of_processes";
foreach $id (keys %hash) {
   next if $id == 0;
   $k = $hash{$id}->{'kmemsize'}->{'barrier'};
   $d = $hash{$id}->{'dcachesize'}->{'limit'};
   $a = $hash{$id}->{'numproc'}->{'maxheld'};
   if ($k < 40*1024*$a+$d) {
      print "VEID $id: Should set kmemsize barrier >= ".(40*1024*$a+$d)." (currently $k)\n\t$URL\n";
   }
}

# Mem alloc limits:
$URL="http://wiki.openvz.org/UBC_consistency_check#Memory_allocation_limits_should_not_be_less_than_the_guarantee";
foreach $id (keys %hash) {
   next if $id == 0;
   $p = $hash{$id}->{'privvmpages'}->{'barrier'};
   $v = $hash{$id}->{'vmguarpages'}->{'barrier'};
   if ($p < $v) {
      print "VEID $id: Should set privvmpages >= vmguarpages (currently $p and $v)\n\t$URL\n";
   }
}

# Buffer sizes
$URL="http://wiki.openvz.org/UBC_consistency_check#Other_TCP_socket_buffers_should_be_big_enough";
foreach $id (keys %hash) {
   next if $id == 0;
   $map = {
      'tcprcvbuf' => 64*1024,
      'tcpsndbuf' => 64*1024,
      'dgramrcvbuf' => 129*1024,
      'othersockbuf' => 129*1024,
   };
   foreach $param (keys %$map) {
      $v = $hash{$id}->{$param}->{'barrier'};
      if ($v < $map->{$param}) {
         print "VEID $id: Should set $param > ".$map->{$param}." (currently $v)\n\t$URL\n";
      }
   }
}

# File limit
$URL="http://wiki.openvz.org/UBC_consistency_check#Number_of_files_limit_should_be_adequate_for_the_expected_number_of_processes";
foreach $id (keys %hash) {
   next if $id == 0;
   $f = $hash{$id}->{'numfile'}->{'barrier'};
   $p = $hash{$id}->{'numproc'}->{'maxheld'};
   if ($f < $p*32) {
      print "VEID $id: Should set numfile to at least ".($p*32)." (currently $f)\n\t$URL\n";
   }
}

# dentry and inode
$URL="http://wiki.openvz.org/UBC_consistency_check#The_limit_on_the_total_size_of_dentry_and_inode_structures_locked_in_memory_should_be_adequate_for_allowed_number_of_files";
foreach $id (keys %hash) {
   next if $id == 0;
   $d = $hash{$id}->{'dcachesize'}->{'barrier'};
   $n = $hash{$id}->{'numfile'}->{'maxheld'};
   if ($d < $n * 384) {
      print "VEID $id: Should set dcachesize to at least ".($n*386)." (currently $d)\n\t$URL\n";
   }
}

# General barrier/limit
$URL="http://wiki.openvz.org/UBC_consistency_check#Barrier_should_be_less_or_equal_than_limit";
foreach $id (keys %hash) {
   next if $id == 0;
   foreach $param (keys %{$hash{$id}}) {
      $b = $hash{$id}->{$param}->{'barrier'};
      $l = $hash{$id}->{$param}->{'limit'};
      if ($b > $l) {
         print "VEID $id: Should set $param limit >= $b (currently $l)\n\t$URL\n";
      }
   }
}

# RAM limits
$URL="http://wiki.openvz.org/UBC_systemwide_configuration#Limiting_memory_allocations";
foreach $id (keys %hash) {
   next if $id == 0;
   $p = $hash{$id}->{'privvmpages'}->{'limit'};
   if ($p*4096 > $mem{'MemTotal'}*0.6) {
      printOut(
         "VEID $id: RAM Allocation set too high.",
         $URL,
         "Formula: privvmpages[limit]*4096 <= 0.6*RAM",
         "\tprivvmpages[limit]*4096 = ".niceSize($p*4096),
         "\t0.6*RAM = ".niceSize($mem{'MemTotal'}*0.6),
         "Consider Dropping privvmpages to ".($mem{'MemTotal'}*0.6/4096)." (".niceSize($mem{'MemTotal'}*0.6).")",
         "unless you know what you are doing or are only running one VE"
      );
      printValues($id, 'privvmpages');
   }
}



print "\nSystem parameters\n-------------------\n";
print "http://wiki.openvz.org/UBC_systemwide_configuration\n";
print "---------------------------------------------------\n";
$lowutil = 0;
$lowcomm = 0;
$ramutil = 0;
$allutil = 0;
$allcomm = 0;
$memutil = 0;
$memcomm = 0;
$memlimit = 0;
foreach $id (keys %hash) {
   next if $id == 0;
   foreach $param ('kmemsize', 'tcprcvbuf', 'tcpsndbuf', 'dgramrcvbuf', 'othersockbuf') {
      $lowutil  +=  $hash{$id}->{$param}->{'held'};
      $lowcomm  +=  $hash{$id}->{$param}->{'limit'};
   }
   $ramutil += $hash{$id}->{'physpages'}->{'held'}*4096;
   $allutil += $hash{$id}->{'oomguarpages'}->{'held'}*4096;
   $allcomm += $hash{$id}->{'oomguarpages'}->{'barrier'}*4096;
   $memutil += $hash{$id}->{'privvmpages'}->{'held'}*4096;
   $memcomm += $hash{$id}->{'vmguarpages'}->{'barrier'}*4096;
   $memlimit += $hash{$id}->{'privvmpages'}->{'limit'}*4096;
}
$ramutil += $lowutil;
$allutil += $lowutil;
$allcomm += $lowcomm;
$memutil += $lowutil;
$memcomm += $lowcomm;

$p = sprintf("%5.3f", $lowutil/0.4/832/1024/1024);
print "Low Memory Utilisation:      $p (out of 1)\n";

$p = sprintf("%5.3f", $lowcomm/0.4/832/1024/1024);
print "Low Memory Commitment:       $p (out of 2)\n";

$p = sprintf("%5.3f", $ramutil/$mem{'MemTotal'});
print "Total RAM Memory Utilisation: $p (out of 1)\n";

$p = sprintf("%5.3f", $allutil/($mem{'MemTotal'} + $mem{'SwapTotal'}));
print "RAM + SWAP Utilization:      $p (out of 1)\n";

$p = sprintf("%5.3f", $allcomm/($mem{'MemTotal'} + $mem{'SwapTotal'}));
print "RAM + SWAP Commitment:       $p (out of about 0.8 - 1)\n";

$p = sprintf("%5.3f", $memutil/($mem{'MemTotal'} + $mem{'SwapTotal'}));
print "Allocated Utilization:       $p (out of unknown)\n";

$p = sprintf("%5.3f", $memcomm/($mem{'MemTotal'} + $mem{'SwapTotal'}));
print "Allocated Commitment:        $p (out of 1)\n";

$p = sprintf("%5.3f", $memlimit/($mem{'MemTotal'} + $mem{'SwapTotal'}));
print "Memory Allocation Limit:     $p (out of 1 to 1.5)\n";





sub printValues {
   my $id = shift;
   my $param = shift;

   my $h = $hash{$id}->{$param}->{'held'};
   my $m = $hash{$id}->{$param}->{'maxheld'};
   my $b = $hash{$id}->{$param}->{'barrier'};
   my $l = $hash{$id}->{$param}->{'limit'};
   print "$param\tcur:$h\tmax:$m\tbar:$b\tlim:$l\n";
}

sub printOut {
   print "\n$_[0]\n";
   shift;
   foreach (@_) {
      print "\t$_\n";
   }
}


sub niceSize {
   my $num = shift;

   return sprintf("%0.2fG", $num/1024/1024/1024) if $num > 1024*1024*1024;
   return sprintf("%0.2fM", $num/1024/1024) if $num > 1024*1024;
   return sprintf("%0.2fK", $num/1024) if $num > 1024;
   return $num;
      
}