#!/usr/bin/perl
#
#
#
#   Authors: Matthew Judge and John Murdoch
#      Date: 17/10/07
#
# A script to check the X&X ESX server farms.
#
# 

use strict;
use URI::Escape;
use IO::Compress::Gzip qw(gzip $GzipError);

$| = 1;

use constant REDO_ERROR => 1024000000;
use constant REDO_WARN  => 409600000;

#
# Define the percentage less than 100 so our bars display
# properly in IE.  Firefox is completely out of the picture.
#
# I need to resolve my bar graphs so I can do away with this hack.
#
use constant PERCENT => 99.2;

#
# Variables related to command line switches
#
my $debug       = 0;
my $verbose     = "";
my $fulllist    = "";
my @ep_email    = ();
my @et_email    = ();
my @servers     = ();

#
# Global list for all the server information
#
my %serverinfo  = ();
my %dsymlink    = ();
my %psymlink    = ();
my %dluns       = ();
my %pluns       = ();
my %dredos      = ();
my %predos      = ();
my %mpreport    = ();

#
# Added to enable warnings in the email body
#
my %warnings    = ();
my $html_buf    = "";
my $mrep_buf    = "";

#
# Used for display formatting
#
my $tot_dev     = 0;
my $tot_pro     = 0;

my ($sec,$min,$hour,$gday,$gmon,$year,$wday,$yday) = localtime(time());
my $htmlfile    = sprintf "ESX_Serverfarm_Overview-%02d-%02d-%04d.html", $gday, $gmon + 1, $year + 1900;
my $mpfile      = sprintf "ESX_MultiPath_Report-%02d-%02d-%04d.html", $gday, $gmon + 1, $year + 1900;

my $boundary    = "_boundary_" . time();

my %dev_servers = (  'lnhiesx13d' => '10.80.167.209',
                     'lnhiesx14d' => '10.80.167.210',
                     'lnhiesx15d' => '10.80.167.211',
                     'lnhiesx16d' => '10.80.167.212',
                     'lnhiesx17d' => '10.80.167.213',
                     'lnhiesx18d' => '10.80.167.214',
                     'lnhiesx20d' => '10.80.167.215',
                     'lnhiesx21d' => '10.80.167.216',
                     'lnhiesx22d' => '10.80.167.217',
                     'lnhiesx23d' => '10.80.167.218',
                     'lnhiesx26d' => '10.80.167.219' );

my %prod_servers = ( 'lnhiesx01'  => '172.24.9.204',
                     'lnhiesx02'  => '172.24.9.205',
                     'lnhiesx03'  => '172.24.9.206',
                     'lnhiesx04'  => '172.24.9.207',
                     'lnhiesx05'  => '172.24.9.208',
                     'lnhiesx99'  => '172.24.9.254' );

my $uname  = getpwuid($<);
my $sshcmd = "/usr/local/sbin/sshadmin";

##################################################################
# Stuff related to the base 64 encoding of the email attachment
#
# Originally written by Steve Neruda for Perl 4.
# Updated for Perl 5 by D.J Gregor.
# (slightly modified to my style of coding).
##################################################################
my $base64_alphabet   = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
my $base64_pad        = '=';
my $uuencode_alphabet = q|`!"#$%&'()*+,-./0123456789:;<=>?|.
                        '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'; # double that '\\'!
my $uuencode_pad      = '`';

my $tr_uuencode       =  " ".$uuencode_alphabet;
   $tr_uuencode       =~ s/(\W)/\\$1/g;
my $tr_base64         =  "A".$base64_alphabet;
   $tr_base64         =~ s/(\W)/\\$1/g;

sub b64encode
{
  local ($_) = @_;
  my $chunk;
  my $result;

  while ( s/^((.|\n){45})// )
  {
    #warn "in:$&:\n";
    $chunk = substr(pack("u", $&), $[+1, 60);
    #warn "packed    :$chunk:\n";
    eval qq
    {
      \$chunk =~ tr|$tr_uuencode|$tr_base64|;
    };
    #warn "translated:$chunk:\n";
    $result .= $chunk . "\n";
  } 

  # any leftover chars go onto a shorter line
  # with uuencode padding converted to base64 padding
  if ( $_ ne "" )
  {
    #warn "length ".length($_)." \$_:$_:\n";
    #warn "enclen ", int((length($_)+2)/3)*4 - (45-length($_))%3, "\n";
    $chunk = substr(pack("u", $_), $[+1,
    int((length($_)+2)/3)*4 - (45-length($_))%3);
    #warn "chunk:$chunk:\n";
    eval qq
    {
      \$chunk =~ tr|$tr_uuencode|$tr_base64|;
    };
    #warn "translated:$chunk:\n";
    $result .= $chunk . ($base64_pad x ((60 - length($chunk)) % 4)) . "\n";
  } 

  # return result
  $result;
} 
##################################################################

my $html_start = "
<HTML>
<HEAD>
<TITLE>XXXXXXXXXXXXX ESX Server Farm Report</TITLE>
</HEAD>

<BODY id=\"body\">

<style type=\"text/css\">
<!--
body
{
  margin: 0 auto;
  color: #000;
  background-color: #fff;
  font-family: Arial, Helvetica;
  font-size: 0.7em;
  text-align: center;
}
div.container
{
  text.align: center;
  padding: 5px;
}
div.server
{
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 5px;
  padding: 4px;
  width: 800px;
  background: #C0D0C0;
  border: 1px solid green;
}

table.info th
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  background: #DDDDDD;
  color: #909090;
}
table.info td
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  background: #FFFFFF;
  color: #404040;
}

table.util
{
  border-width: 1px;
  border-style: outset;
  margin: 0;
  margin-bottom: 8px;
  padding: 0;
  spacing: 0;
  width: 100%;
}
table.util th
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  text-align: center;
  font-size: 0.7em;
}
table.util td
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  text-align: left;
  font-size: 0.6em;
}

table.outer
{
  border-width: 1px;
  border-style: outset;
  padding: 0;
  margin: 0;
  spacing: 3px;
  margin-bottom: 4px;
  Width: 100%;
}
table.outer th
{
  spacing: 0px;
  padding: 0px 4px 0px 4px;
  color: #C0F0FF;
  text-align: center;
  font-size: 0.7em;
}
table.outer td
{
  spacing: 0px;
  padding: 0px 4px 0px 4px;
  color: #FFFFFF;
  text-align: left;
  font-size: 0.6em;
}

#dead
{
  background: black;
  color: white;
  padding: 0px 4px 0px 4px;
}
#off
{
  background: red;
  color: white;
  padding: 0px 4px 0px 4px;
}
#on
{
  background: #40B040;
  color: white;
  padding: 0px 4px 0px 4px;
}
#idle
{
  color: white;
  padding: 0px 4px 0px 4px;
}
#ok
{
  background: #40B040;
  color: white;
  padding: 0px 4px 0px 4px;
}
#active
{
  background: #F09010;
  color: white;
  padding: 0px 4px 0px 4px;
}
#pref
{
  background: #D040D0;
  color: white;
  padding: 0px 4px 0px 4px;
}
-->
</style>
";

my $html_cont = "
<style type=\"text/css\">
<!--
table.on th
{
 background: #007070;
}
table.on td
{
 background: #508080;
}
table.off th
{
  background: #3F683F;
  color: #EEEEEE;
}
table.off td
{
  background: #99AA99;
  color: #404040;
}

table.dusage td
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  background: #A0B0A0;
}
table.dusage th
{
  spacing: 0;
  padding: 0px 4px 0px 4px;
  background: #608060;
}

#inner
{
  margin: 0;
  padding: 0;
  spacing: 0;
  width: 100%;
  text-align: left;
}
#inner td
{
  margin: 0;
  padding: 0;
  text-align: left;
}
#inner th
{
  spacing: 0;
  padding: 0;
  text-align: center;
}

#even th
{
  background: #608060;
  color: #F0F0F0;
}
#even td
{
  background: #A0B0A0;
  color: #FFFFFF;
}

#odd th
{
  background: #608070;
  color: #F0F0F0;
}
#odd td
{
  background: #90A090;
  color: #FFFFFF;
}

#alert
{
  background: red;
  color: white;
  padding: 0px 4px 0px 4px;
}

#warn
{
  background: orange;
  color: white;
  padding: 0px 4px 0px 4px;
}

.graph
{
  width: 100%;
  font-size: 5px;
  height: 5px;
  margin: 2px 4px 2px 4px;
  border: 1px solid #555;
  white-space: nowrap;
  text-overflow: hidden;
  overflow: hidden;
}
.graph ul,
.graph ul li
{
  list-style: none;
  margin: 0;
  padding: 0;
}
.graph ul
{
  text-align: center;
}
.graph ul li
{
  list-style: none outside;
  float: left;
  font-size: 4px;
  height: 100%;
}
-->
</style>

<script type=\"text/javascript\">
/*
  SortTable
  version 2
  7th April 2007
  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
  
  Thanks to many, many people for contributions and suggestions.
  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
  This basically means: do what you want with it.
*/

 
var stIsIE = /*\@cc_on!\@*/false;

sorttable = {
  init: function() {
    // quit if this function has already been called
    if (arguments.callee.done) return;
    // flag this function so we don't do the same thing twice
    arguments.callee.done = true;
    // kill the timer
    if (_timer) clearInterval(_timer);
    
    if (!document.createElement || !document.getElementsByTagName) return;
    
    sorttable.DATE_RE = /^(\\\d\\\d?)[\\\/\\\.-](\\\d\\\d?)[\\\/\\\.-]((\\\d\\\d)?\\\d\\\d)\$/;
    
    forEach(document.getElementsByTagName('table'), function(table) {
      if (table.className.search(/\\bsortable\\b/) != -1) {
        sorttable.makeSortable(table);
      }
    });
    
  },
  
  makeSortable: function(table) {
    if (table.getElementsByTagName('thead').length == 0) {
      // table doesn't have a tHead. Since it should have, create one and
      // put the first table row in it.
      the = document.createElement('thead');
      the.appendChild(table.rows[0]);
      table.insertBefore(the,table.firstChild);
    }
    // Safari doesn't support table.tHead, sigh
    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
    
    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
    
    // Sorttable v1 put rows with a class of \"sortbottom\" at the bottom (as
    // \"total\" rows, for example). This is B&R, since what you're supposed
    // to do is put them in a tfoot. So, if there are sortbottom rows,
    // for backwards compatibility, move them to tfoot (creating it if needed).
    sortbottomrows = [];
    for (var i=0; i<table.rows.length; i++) {
      if (table.rows[i].className.search(/\\bsortbottom\\b/) != -1) {
        sortbottomrows[sortbottomrows.length] = table.rows[i];
      }
    }
    if (sortbottomrows) {
      if (table.tFoot == null) {
        // table doesn't have a tfoot. Create one.
        tfo = document.createElement('tfoot');
        table.appendChild(tfo);
      }
      for (var i=0; i<sortbottomrows.length; i++) {
        tfo.appendChild(sortbottomrows[i]);
      }
      delete sortbottomrows;
    }
    
    // work through each column and calculate its type
    headrow = table.tHead.rows[0].cells;
    for (var i=0; i<headrow.length; i++) {
      // manually override the type with a sorttable_type attribute
      if (!headrow[i].className.match(/\\bsorttable_nosort\\b/)) { // skip this col
        mtch = headrow[i].className.match(/\\bsorttable_([a-z0-9]+)\\b/);
        if (mtch) { override = mtch[1]; }
	      if (mtch && typeof sorttable[\"sort_\"+override] == 'function') {
	        headrow[i].sorttable_sortfunction = sorttable[\"sort_\"+override];
	      } else {
	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
	      }
	      // make it clickable to sort
	      headrow[i].sorttable_columnindex = i;
	      headrow[i].sorttable_tbody = table.tBodies[0];
	      dean_addEvent(headrow[i],\"click\", function(e) {

          if (this.className.search(/\\bsorttable_sorted\\b/) != -1) {
            // if we're already sorted by this column, just 
            // reverse the table, which is quicker
            sorttable.reverse(this.sorttable_tbody);
            this.className = this.className.replace('sorttable_sorted',
                                                    'sorttable_sorted_reverse');
            this.removeChild(document.getElementById('sorttable_sortfwdind'));
            sortrevind = document.createElement('span');
            sortrevind.id = \"sorttable_sortrevind\";
            sortrevind.innerHTML = stIsIE ? '&nbsp<font face=\"webdings\">5</font>' : ' &#x25B4;';
            this.appendChild(sortrevind);
            return;
          }
          if (this.className.search(/\\bsorttable_sorted_reverse\\b/) != -1) {
            // if we're already sorted by this column in reverse, just 
            // re-reverse the table, which is quicker
            sorttable.reverse(this.sorttable_tbody);
            this.className = this.className.replace('sorttable_sorted_reverse',
                                                    'sorttable_sorted');
            this.removeChild(document.getElementById('sorttable_sortrevind'));
            sortfwdind = document.createElement('span');
            sortfwdind.id = \"sorttable_sortfwdind\";
            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face=\"webdings\">6</font>' : ' &#x25BE;';
            this.appendChild(sortfwdind);
            return;
          }
          
          // remove sorttable_sorted classes
          theadrow = this.parentNode;
          forEach(theadrow.childNodes, function(cell) {
            if (cell.nodeType == 1) { // an element
              cell.className = cell.className.replace('sorttable_sorted_reverse','');
              cell.className = cell.className.replace('sorttable_sorted','');
            }
          });
          sortfwdind = document.getElementById('sorttable_sortfwdind');
          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
          sortrevind = document.getElementById('sorttable_sortrevind');
          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
          
          this.className += ' sorttable_sorted';
          sortfwdind = document.createElement('span');
          sortfwdind.id = \"sorttable_sortfwdind\";
          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face=\"webdings\">6</font>' : ' &#x25BE;';
          this.appendChild(sortfwdind);

	        // build an array to sort. This is a Schwartzian transform thing,
	        // i.e., we \"decorate\" each row with the actual sort key,
	        // sort based on the sort keys, and then put the rows back in order
	        // which is a lot faster because you only do getInnerText once per row
	        row_array = [];
	        col = this.sorttable_columnindex;
	        rows = this.sorttable_tbody.rows;
	        for (var j=0; j<rows.length; j++) {
	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
	        }
	        /* If you want a stable sort, uncomment the following line */
	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
	        /* and comment out this one */
	        row_array.sort(this.sorttable_sortfunction);
	        
	        tb = this.sorttable_tbody;
	        for (var j=0; j<row_array.length; j++) {
	          tb.appendChild(row_array[j][1]);
	        }
	        
	        delete row_array;
	      });
	    }
    }
  },
  
  guessType: function(table, column) {
    // guess the type of a column based on its first non-blank row
    sortfn = sorttable.sort_alpha;
    for (var i=0; i<table.tBodies[0].rows.length; i++) {
      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
      if (text != '') {
        if (text.match(/^-?[£\$¤]?[\\\d,.]+%?\$/)) {
          return sorttable.sort_numeric;
        }
        // check for a date: dd/mm/yyyy or dd/mm/yy 
        // can have / or . or - as separator
        // can be mm/dd as well
        possdate = text.match(sorttable.DATE_RE)
        if (possdate) {
          // looks like a date
          first = parseInt(possdate[1]);
          second = parseInt(possdate[2]);
          if (first > 12) {
            // definitely dd/mm
            return sorttable.sort_ddmm;
          } else if (second > 12) {
            return sorttable.sort_mmdd;
          } else {
            // looks like a date, but we can't tell which, so assume
            // that it's dd/mm (English imperialism!) and keep looking
            sortfn = sorttable.sort_ddmm;
          }
        }
      }
    }
    return sortfn;
  },
  
  getInnerText: function(node) {
    // gets the text we want to use for sorting for a cell.
    // strips leading and trailing whitespace.
    // this is *not* a generic getInnerText function; it's special to sorttable.
    // for example, you can override the cell text with a customkey attribute.
    // it also gets .value for <input> fields.
    
    hasInputs = (typeof node.getElementsByTagName == 'function') &&
                 node.getElementsByTagName('input').length;
    
    if (node.getAttribute(\"sorttable_customkey\") != null) {
      return node.getAttribute(\"sorttable_customkey\");
    }
    else if (typeof node.textContent != 'undefined' && !hasInputs) {
      return node.textContent.replace(/^\s+|\s+\$/g, '');
    }
    else if (typeof node.innerText != 'undefined' && !hasInputs) {
      return node.innerText.replace(/^\s+|\s+\$/g, '');
    }
    else if (typeof node.text != 'undefined' && !hasInputs) {
      return node.text.replace(/^\s+|\s+\$/g, '');
    }
    else {
      switch (node.nodeType) {
        case 3:
          if (node.nodeName.toLowerCase() == 'input') {
            return node.value.replace(/^\s+|\s+\$/g, '');
          }
        case 4:
          return node.nodeValue.replace(/^\s+|\s+\$/g, '');
          break;
        case 1:
        case 11:
          var innerText = '';
          for (var i = 0; i < node.childNodes.length; i++) {
            innerText += sorttable.getInnerText(node.childNodes[i]);
          }
          return innerText.replace(/^\s+|\s+\$/g, '');
          break;
        default:
          return '';
      }
    }
  },
  
  reverse: function(tbody) {
    // reverse the rows in a tbody
    newrows = [];
    for (var i=0; i<tbody.rows.length; i++) {
      newrows[newrows.length] = tbody.rows[i];
    }
    for (var i=newrows.length-1; i>=0; i--) {
       tbody.appendChild(newrows[i]);
    }
    delete newrows;
  },
  
  /* sort functions
     each sort function takes two parameters, a and b
     you are comparing a[0] and b[0] */
  sort_numeric: function(a,b) {
    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
    if (isNaN(aa)) aa = 0;
    bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); 
    if (isNaN(bb)) bb = 0;
    return aa-bb;
  },
  sort_alpha: function(a,b) {
    if (a[0]==b[0]) return 0;
    if (a[0]<b[0]) return -1;
    return 1;
  },
  sort_ddmm: function(a,b) {
    mtch = a[0].match(sorttable.DATE_RE);
    y = mtch[3]; m = mtch[2]; d = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt1 = y+m+d;
    mtch = b[0].match(sorttable.DATE_RE);
    y = mtch[3]; m = mtch[2]; d = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt2 = y+m+d;
    if (dt1==dt2) return 0;
    if (dt1<dt2) return -1;
    return 1;
  },
  sort_mmdd: function(a,b) {
    mtch = a[0].match(sorttable.DATE_RE);
    y = mtch[3]; d = mtch[2]; m = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt1 = y+m+d;
    mtch = b[0].match(sorttable.DATE_RE);
    y = mtch[3]; d = mtch[2]; m = mtch[1];
    if (m.length == 1) m = '0'+m;
    if (d.length == 1) d = '0'+d;
    dt2 = y+m+d;
    if (dt1==dt2) return 0;
    if (dt1<dt2) return -1;
    return 1;
  },
  
  shaker_sort: function(list, comp_func) {
    // A stable sort function to allow multi-level sorting of data
    // see: http://en.wikipedia.org/wiki/Cocktail_sort
    // thanks to Joseph Nahmias
    var b = 0;
    var t = list.length - 1;
    var swap = true;

    while(swap) {
        swap = false;
        for(var i = b; i < t; ++i) {
            if ( comp_func(list[i], list[i+1]) > 0 ) {
                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
                swap = true;
            }
        } // for
        t--;

        if (!swap) break;

        for(var i = t; i > b; --i) {
            if ( comp_func(list[i], list[i-1]) < 0 ) {
                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
                swap = true;
            }
        } // for
        b++;

    } // while(swap)
  } 
}


/* ******************************************************************
   Supporting functions: bundled here to avoid depending on a library
   ****************************************************************** */

// Dean Edwards/Matthias Miller/John Resig

/* for Mozilla/Opera9 */
if (document.addEventListener) {
    document.addEventListener(\"DOMContentLoaded\", sorttable.init, false);
}

/* for Internet Explorer */
/*\@cc_on \@*/
/*\@if (\@_win32)
    document.write(\"<script id=__ie_onload defer src=javascript:void(0)><\\/script>\");
    var script = document.getElementById(\"__ie_onload\");
    script.onreadystatechange = function() {
        if (this.readyState == \"complete\") {
            sorttable.init(); // call the onload handler
        }
    };
/*\@end \@*/

/* for Safari */
if (/WebKit/i.test(navigator.userAgent)) { // sniff
    var _timer = setInterval(function() {
        if (/loaded|complete/.test(document.readyState)) {
            sorttable.init(); // call the onload handler
        }
    }, 10);
}

/* for other browsers */
window.onload = sorttable.init;

// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini

// http://dean.edwards.name/weblog/2005/10/add-event/

function dean_addEvent(element, type, handler) {
        if (element.addEventListener) {
                element.addEventListener(type, handler, false);
        } else {
                // assign each event handler a unique ID
                if (!handler.\$\$guid) handler.\$\$guid = dean_addEvent.guid++;
                // create a hash table of event types for the element
                if (!element.events) element.events = {};
                // create a hash table of event handlers for each element/event pair
                var handlers = element.events[type];
                if (!handlers) {
                        handlers = element.events[type] = {};
                        // store the existing event handler (if there is one)
                        if (element[\"on\" + type]) {
                                handlers[0] = element[\"on\" + type];
                        }
                }
                // store the event handler in the hash table
                handlers[handler.\$\$guid] = handler;
                // assign a global event handler to do all the work
                element[\"on\" + type] = handleEvent;
        }
};

// a counter used to create unique IDs
dean_addEvent.guid = 1;

function removeEvent(element, type, handler) {
        if (element.removeEventListener) {
                element.removeEventListener(type, handler, false);
        } else {
                // delete the event handler from the hash table
                if (element.events && element.events[type]) {
                        delete element.events[type][handler.\$\$guid];
                }
        }
};

function handleEvent(event) {
        var returnValue = true;
        // grab the event object (IE uses a global event object)
        event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
        // get a reference to the hash table of event handlers
        var handlers = this.events[event.type];
        // execute each event handler
        for (var i in handlers) {
                this.\$\$handleEvent = handlers[i];
                if (this.\$\$handleEvent(event) === false) {
                        returnValue = false;
                }
        }
        return returnValue;
};

function fixEvent(event) {
        // add W3C standard event methods
        event.preventDefault = fixEvent.preventDefault;
        event.stopPropagation = fixEvent.stopPropagation;
        return event;
};
fixEvent.preventDefault = function() {
        this.returnValue = false;
};
fixEvent.stopPropagation = function() {
  this.cancelBubble = true;
}

// Dean's forEach: http://dean.edwards.name/base/forEach.js
/*
        forEach, version 1.0
        Copyright 2006, Dean Edwards
        License: http://www.opensource.org/licenses/mit-license.php
*/

// array-like enumeration
if (!Array.forEach) { // mozilla already supports this
        Array.forEach = function(array, block, context) {
                for (var i = 0; i < array.length; i++) {
                        block.call(context, array[i], i, array);
                }
        };
}

// generic enumeration
Function.prototype.forEach = function(object, block, context) {
        for (var key in object) {
                if (typeof this.prototype[key] == \"undefined\") {
                        block.call(context, object[key], key, object);
                }
        }
};

// character enumeration
String.forEach = function(string, block, context) {
        Array.forEach(string.split(\"\"), function(chr, index) {
                block.call(context, chr, index, string);
        });
};

// globally resolve forEach enumeration
var forEach = function(object, block, context) {
        if (object) {
                var resolve = Object; // default
                if (object instanceof Function) {
                        // functions have a \"length\" property
                        resolve = Function;
                } else if (object.forEach instanceof Function) {
                        // the object implements a custom forEach method so use that
                        object.forEach(block, context);
                        return;
                } else if (typeof object == \"string\") {
                        // the object is a string
                        resolve = String;
                } else if (typeof object.length == \"number\") {
                        // the object is array-like
                        resolve = Array;
                }
                resolve.forEach(object, block, context);
        }
};
</script>
";

my $begin_mpreport = "
<!-- Open server container -->
<div class=\"server\">
<h3>Multipath Report for %s</h3>

  <table class=\"util info\">
  <tr>
    <th style=\"width: 20%;\">Disk</th>
    <th colspan=\"3\" style=\"width: 20%\">Path 1</th>
    <th colspan=\"3\" style=\"width: 20%\">Path 2</th>
    <th colspan=\"3\" style=\"width: 20%\">Path 3</th>
    <th colspan=\"3\" style=\"width: 20%\">Path 4</th>
  </tr>
";

my $mpreport_line = "
  <tr>
    <td style=\"width: 20%;\">%s</td>

    <td id=\"%s\" style=\"width:2px\"></td>
    <td id=\"%s\" style=\"width:2px\"></td>
    <td style=\"width: 156px\">%s</td>

    <td id=\"%s\" style=\"width:2px\"></td>
    <td id=\"%s\" style=\"width:2px\"></td>
    <td style=\"width: 156px\">%s</td>

    <td id=\"%s\" style=\"width:2px\"></td>
    <td id=\"%s\" style=\"width:2px\"></td>
    <td style=\"width: 156px\">%s</td>

    <td id=\"%s\" style=\"width:2px\"></td>
    <td id=\"%s\" style=\"width:2px\"></td>
    <td style=\"width: 156px\">%s</td>
  </tr>
";

my $close_mpreport = "
  </table>
<!-- Close the server container -->
</div>
";

my $begin_server = "
<!-- Open server container -->
<div class=\"server\">
<h3>%s</h3>
";

my $vm_util = "
  <table class=\"util %s\">
  <tr>
    <th style=\"width: 16%;\"> </th>
    <th colspan=2 style=\"width: 42%\">CPU Utilisation %</th>
    <th colspan=2 style=\"width: 42%\">Memory Utilisation %</th>
  </tr>
  <tr><td style=\"width: 16%;text-align: right;\">Virtual Machines </td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>
    </td>
    <td style=\"width: 7%;\"> %2.1f %%</td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>

    </td>
    <td style=\"width: 7%;\"> %s</td>
  </tr>
  <tr><td style=\"width: 16%;text-align: right;\">Other </td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>
    </td>
    <td style=\"width: 7%;\"> %2.1f %%</td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>

    </td>
    <td style=\"width: 7%;\"> %s</td>
  </tr>
  <tr><td style=\"width: 16%;text-align: right;\">System Total </td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>
    </td>
    <td style=\"width: 7%;\"> %2.1f %%</td>
    <td>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF9900;\"> </li>
         <li style=\"width:%2.1f%%;background: #FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>

    </td>
    <td style=\"width: 7%;\">%s</td>
  </tr>
  </table>
";

my $vm_phys_mem = "
  <table class=\"util info\">

  <tr>
    <th style=\"text-align:left;\" colspan=6>Physical Memory</th>
  </tr>
  <tr>

    <td colspan=6>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #5588B0;\"> </li>
         <li style=\"width:%2.1f%%;background: #47A3FF;\"> </li>
         <li style=\"width:%2.1f%%;background: #66D055;\"> </li>
         <li style=\"width:%2.1f%%;background: #20A020;\"> </li>
         <li style=\"width:%2.1f%%;background: #EEEEEE;\"> </li>

      </ul>
      </div>

    </td>
   </tr>

   <tr>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5588B0;Width:15%;float:left;height: 9px;\"></div>Virtual Machines</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#47A3FF;Width:15%;float:left;height: 9px;\"></div>Shared Common</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#66D055;Width:15%;float:left;height: 9px;\"></div>Virtualisation</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#20A020;Width:15%;float:left;height: 9px;\"></div>Service Console</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#EEEEEE;Width:15%;float:left;height: 9px;\"></div>Free</td>
     <td style=\"width:16%\">Total</td>
   </tr>

   <tr>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
   </tr>

   <tr>
     <td colspan=6 style=\"width:16%\">Memory Savings Due to Sharing: %s</td>
   </tr>

   </table>
";

my $vm_res_mem = "
  <table class=\"util info\">

  <tr>
    <th style=\"text-align:left\" colspan=6>Reserved Memory</th>
  </tr>

  <tr>
    <td colspan=3 style=\"text-align:center\">RAM</td><td colspan=3 style=\"text-align:center\">Swap</td>
  </tr>

  <tr>

    <td colspan=3>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #5588D0;\"> </li>
         <li style=\"width:%2.1f%%;background: #5050A0;\"> </li>
         <li style=\"width:%2.1f%%;background: #D0D0D0;\"> </li>

      </ul>
      </div>
    </td>

    <td colspan=3>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background: #5588D0;\"> </li>
         <li style=\"width:%2.1f%%;background: #5050A0;\"> </li>
         <li style=\"width:%2.1f%%;background: #D0D0D0;\"> </li>

      </ul>
      </div>
    </td>

   </tr>

   <tr>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5588D0;Width:15%;float:left;height: 9px;\"></div>Reserved</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5050A0;Width:15%;float:left;height: 9px;\"></div>Unreserved</td>
     <td style=\"width:16%\">Total</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5588D0;Width:15%;float:left;height: 9px;\"></div>Reserved</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5050A0;Width:15%;float:left;height: 9px;\"></div>Unreserved</td>
     <td style=\"width:16%\">Total</td>
   </tr>

   <tr>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
   </tr>

   <tr>
     <td colspan=6 style=\"width:16%\">Memory Available to Power On a Virtual Machine: %s (1P), %s (2P)</td>
   </tr>

  </table>
";

my $vm_mem_usage = "
  <table class=\"util info\">

  <tr>
    <th style=\"text-align:left;\" colspan=6>Virtual Machines (%d)</th>
  </tr>
  <tr>

    <td colspan=6>
      <div class=\"graph\">
      <ul>

         <li style=\"width:%2.1f%%;background:#5588D0;\"> </li>
         <li style=\"width:%2.1f%%;background:#5050A0;\"> </li>
         <li style=\"width:%2.1f%%;background:#FF4040;\"> </li>
         <li style=\"width:%2.1f%%;background:#D0D040;\"> </li>
         <li style=\"width:%2.1f%%;background:#D0D0D0;\"> </li>

      </ul>
      </div>

    </td>
   </tr>

   <tr>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5588D0;Width:15%;float:left;height:9px;\"></div>Private</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#5050A0;Width:15%;float:left;height:9px;\"></div>Shared</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#FF4040;Width:15%;float:left;height:9px;\"></div>Swapped</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#D0D040;Width:15%;float:left;height:9px;\"></div>Balloon</td>
     <td style=\"width:16%\"><div class=\"graph\" style=\"background:#D0D0D0;Width:15%;float:left;height:9px;\"></div>Unused</td>
     <td style=\"width:16%\">Total</td>
   </tr>

   <tr>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
     <td style=\"width:16%\">%s</td>
   </tr>

  </table>
";

my $vm_begin = "
  <table class=\"outer %s\">
  <tr>
    <th style=\"text-align:left;\">%s</th>
    <th style=\"width:55px;\">RAM</th>
    <th style=\"width:55px;\">
      <div class=\"graph\" style=\"background:#5588D0;Width:50%;height:9px;\"></div>
    </th>
    <th style=\"width:55px;\">
      <div class=\"graph\" style=\"background:#5050A0;Width:50%;height:9px;\"></div>
    </th>
    <th style=\"width:55px;\">
      <div class=\"graph\" style=\"background:#FF4040;Width:50%;height:9px;\"></div>
    </th>
    <th style=\"width:55px;\">
      <div class=\"graph\" style=\"background:#D0D040;Width:50%;height:9px;\"></div>
    </th>
    <th style=\"width:55px;\">
      <div class=\"graph\" style=\"background:#D0D0D0;Width:50%;height:9px;\"></div>
    </th>
    <th style=\"width:55px;\">Active</th>
  </tr>
  <tr>
    <td rowspan=2>
      <table id=\"inner\">
        <tr><td>Guest OS: %s | Powered %s</td></tr>

        <tr><td>CPUs: %d | Disks / Present: %d/%d | Uptime: %s</td></tr>
      </table>
    </td>
    <td rowspan=2 style=\"text-align:center;\">%s</td>
";
my $vm_graph = "
    <td colspan=5>
      <div class=\"graph\">
      <ul>

        <li style=\"width:%2.1f%%;background:#5588D0;\"> </li>
        <li style=\"width:%2.1f%%;background:#5050A0;\"> </li>
        <li style=\"width:%2.1f%%;background:#FF4040;\"> </li>
        <li style=\"width:%2.1f%%;background:#D0D040;\"> </li>
        <li style=\"width:%2.1f%%;background:#D0D0D0;\"> </li>

      </ul>
      </div>
    </td>
";
my $vm_memory = "
    <td rowspan=2 style=\"text-align:center;\">%s</td>
  </tr>

  <tr>
    <td style=\"width:55px;text-align:center;\">%s</td>
    <td style=\"width:55px;text-align:center;\">%s</td>
    <td style=\"width:55px;text-align:center;%s\">%s</td>
    <td style=\"width:55px;text-align:center;%s\">%s</td>
    <td style=\"width:55px;text-align:center;\">%s</td>
  </tr>
";
my $vm_details = "
  <tr>    
    <td>
      <table id=\"inner\">   
        <tr><td>VM Tools: %s | VM State: %s | Heartbeat: %s</td></tr>
        <tr><td>Sync Time: %s | PID %s | VMID %s</td></tr>
      </table>
    </td>
    <td colspan=8>

      <table id=\"inner\" style=\"width:100%\">
      <tr>
        <td style=\"width:50px;text-align:right;\">% CPU:</td>
        <td%s>
          <div class=\"graph\">
          <ul>

             <li style=\"width:%2.1f%%;background:#66D055;\"> </li>
             <li style=\"width:%2.1f%%;background:#FF9900;\"> </li>
             <li style=\"width:%2.1f%%;background:#FF4040;\"> </li>
             <li style=\"width:%2.1f%%;background:#EEEEEE;\"> </li>

          </ul>
          </div>
        </td>
      </tr>
      <tr>
        <td style=\"width:50px;text-align:right;\">% Mem:</td>
        <td%s>
          <div class=\"graph\">
          <ul>

             <li style=\"width:%2.1f%%;background:#66D055;\"> </li>
             <li style=\"width:%2.1f%%;background:#FF9900;\"> </li>
             <li style=\"width:%2.1f%%;background:#FF4040;\"> </li>
             <li style=\"width:%2.1f%%;background:#EEEEEE;\"> </li>

          </ul>
          </div>
        </td>
      </tr>
      </table>

    </td>
  </tr>   

  <tr>
    <td colspan=\"8\">
      <table id=\"inner\">
      <tr>
        <td width=\"100%\">Server: %s | Config file: %s</td>
      </tr>
      </table>
    </td>
  </tr>   

  </table>
";

my $disk_usage = "
<div class=server>
<h3>Disk Usage (%s)</h3>
<table class=\"outer dusage sortable\" onselectstart=\"return false\">
  <thead>
  <tr>
   <th class=\"sorttable_alpha\" style=\"width:23%;text-align:left;\">Mount Point</th>
   <th class=\"sorttable_numeric\" style=\"width:22%;text-align:left;\">Symbolic Link</th>
   <th class=\"sorttable_numeric\" style=\"width:11%;text-align:right;\">Disks</th>
   <th class=\"sorttable_numeric\" style=\"width:11%;text-align:right;\">VMs</th>
   <th class=\"sorttable_numeric\" style=\"width:11%;text-align:right;\">Size</th>
   <th class=\"sorttable_numeric\" style=\"width:11%;text-align:right;\">Used</th>
   <th class=\"sorttable_numeric\" style=\"width:11%;text-align:right;\">Avail</th>
  </tr>
  </thead>

  <tbody id=\"tbody\">
";
my $usage_line = "
  <tr>
   <td style=\"width:23%;text-align:left;\">%s</td>
   <td style=\"width:22%;text-align:left;\">%s</td>
   <td style=\"width:11%;text-align:right;\">%s</td>
   <td style=\"width:11%;text-align:right;\">%s</td>
   <td style=\"width:11%;text-align:right;\">%s</td>
   <td style=\"width:11%;text-align:right;\">%s</td>
   <td style=\"width:11%;text-align:right;\">%s</td>
  </tr>
";
my $close_usage = "

  </tbody>
</table>
</div>
";

my $redo_files = "
<div class=server>
<h3>REDO Files (%s)</h3>
<table class=\"outer dusage\" onselectstart=\"return false\">
  <thead>
  <tr>
   <th style=\"width:22%;text-align:left;\" colspan=\"2\">Details</th>
   <th style=\"width:78%;text-align:left;\">Choice of commands to commit the REDO file</th>
  </tr>
  </thead>

  <tbody id=\"tbody\">

";               
my $redo_line = "
  <tr id=\"%s\">
   <th style=\"width:11%;text-align:right;\">Server:</th>
   <td style=\"width:11%;text-align:left;\">%s</td>
   <td style=\"width:78%;text-align:left;\">vmware-cmd \"%s</td>
  </tr>

  <tr id=\"%s\">
   <th style=\"width:11%;text-align:right;\">Created:</th>
   <td style=\"width:11%;\">%s</td>
   <td style=\"width:78%;text-align:left;\">vmCommit.pl -c \"%s\"</td>
  </tr>

  <tr id=\"%s\">
   <th style=\"width:11%;text-align:right;\">Size:</th>
   <td style=\"width:11%;text-align:left;%s\">%s</td>
   <td style=\"width:78%;text-align:left;\">vmkfstools -m %s <span style=\"color:#902020\">(Run when powered off only)</span></td>
  </tr>

";
my $close_redo = "
  </tbody>
</table>
</div>
";

#
# Lazy man's debug
#
sub debug_print
{
  my $level = shift;

  if ( $debug >= $level )
  {
    print STDERR "DEBUG: @_";
  }
}

sub show_disk_usage
{
  debug_print 2, "Number of LUNs (D) = " . keys(%dluns) . ", Total hosts = $tot_dev\n";
  if ( keys(%dluns) > 0 && $tot_dev > 0 )
  {
    $html_buf .= sprintf $disk_usage, "Dev/CTE Servers";
    foreach my $fs (sort(keys(%dluns)))
    {
      debug_print 7, "show_disk_usage: \$fs = $fs\n";

      if ( defined %{$dluns{$fs}->{'vms'}} )
      {
        my $length = keys(%{$dluns{$fs}->{'vms'}});

        debug_print 7, "show_disk_usage: $length\n";

        $html_buf .= sprintf $usage_line,
                     $dluns{$fs}->{'mount'}, $dluns{$fs}->{'symlink'}, $dluns{$fs}->{'vdisks'}, $length,
                     pretty_print($dluns{$fs}->{'size'}), pretty_print($dluns{$fs}->{'used'}),
                     pretty_print($dluns{$fs}->{'avail'});
      }
    }
    $html_buf .= $close_usage;
  } 

  debug_print 2, "Number of LUNs (P) = " . keys(%pluns) . ", Total hosts = $tot_pro\n";
  if ( keys(%pluns) > 0 && $tot_pro > 0 )
  {
    $html_buf .= sprintf $disk_usage, "Production Servers";
    foreach my $fs (sort(keys(%pluns)))
    {
      debug_print 7, "show_disk_usage: \$fs = $fs\n";

      if ( defined %{$pluns{$fs}->{'vms'}} )
      {
        my $length = keys(%{$pluns{$fs}->{'vms'}});

        debug_print 7, "show_disk_usage: $length\n";

        $html_buf .= sprintf $usage_line,
                     $pluns{$fs}->{'mount'}, $pluns{$fs}->{'symlink'}, $pluns{$fs}->{'vdisks'}, $length,
                     pretty_print($pluns{$fs}->{'size'}), pretty_print($pluns{$fs}->{'used'}),
                     pretty_print($pluns{$fs}->{'avail'});
      }
    } 
    $html_buf .= $close_usage;
  }
}

sub show_redo_files
{
  my $colour = "odd";

  if ( keys(%dredos) > 0 )
  {
    $html_buf .= sprintf $redo_files, "Dev/CTE Servers";
    foreach my $redo (sort(keys(%dredos)))
    {
      $html_buf .= sprintf $redo_line,
                   $colour,
                   $dredos{$redo}->{'server'},
                   $dredos{$redo}->{'cfgfile'} . "\" commit scsi" . $dredos{$redo}->{'disk'} . " 0 0 1",
                   $colour,
                   $dredos{$redo}->{'cdate'}, $dredos{$redo}->{'cfgfile'},
                   $colour,
                   ($dredos{$redo}->{'size'} > REDO_ERROR) ? "background:red;color:white;" :
                        ($dredos{$redo}->{'size'} > REDO_WARN) ? "background:orange;color:white;" : "",
                   pretty_print($dredos{$redo}->{'size'} / 1024),
                   $dredos{$redo}->{'devfile'} . $dredos{$redo}->{'ext'};

      $colour = $colour eq "odd" ? "even" : "odd";
    }
    $html_buf .= $close_redo;
  }

  if ( keys(%predos) > 0 )
  {
    $html_buf .= sprintf $redo_files, "Production Servers";
    foreach my $redo (sort(keys(%predos)))
    {
      $html_buf .= sprintf $redo_line,
                   $colour,
                   $predos{$redo}->{'server'},
                   $predos{$redo}->{'cfgfile'} . "\" commit scsi" . $predos{$redo}->{'disk'} . " 0 0 1",
                   $colour,
                   $dredos{$redo}->{'cdate'}, $predos{$redo}->{'cfgfile'},
                   $colour,
                   ($dredos{$redo}->{'size'} > 1024000000) ? "background:red;color:white;" :
                        ($dredos{$redo}->{'size'} > 409600000) ? "background:orange;color:white;" : "",
                   pretty_print($predos{$redo}->{'size'} / 1024),
                   $predos{$redo}->{'devfile'} . $predos{$redo}->{'ext'};

      $colour = $colour eq "odd" ? "even" : "odd";
    }
    $html_buf .= $close_redo;
  }
}

sub vprint
{
  if ( $verbose )
  {
    print STDERR @_;
  }
}

# wget-style. routine by tachyon
# at http://tachyon.perlmonk.org/
sub progress_bar
{
    my ( $got, $total ) = @_;

    my $width = 40;
    my $char  = '=';

    my $num_width = $total;

    sprintf " %3d% |%-${width}s|     \r", ((100 * $got) / +$total), $char x (($width-1)*$got/$total) . '>';
}

#
# Expect values in Kbytes
#
sub pretty_print
{
  my $size = shift;

  # Setup some common file size measurements.
  my $kb = 1024;           # Kilobyte
  my $mb = 1048576;        # Megabyte
  my $gb = 1073741824;     # Gigabyte
  my $tb = 1099511627776;  # Terabyte

  if ( $size < $kb )
  {
    return sprintf "%d K", $size;
  }
  elsif ( $size < $mb )
  {
    return sprintf "%2.1f M", $size/$kb;
  }
  elsif ( $size < $gb )
  {
    return sprintf "%2.1f G", $size/$mb;
  }
  else
  {
    return sprintf "%2.1f T", $size/$gb;
  }
}

sub print_2_console
{
  my ($server)    = shift;

  my %vmlist      = %{$serverinfo{$server}->{'vmlist'}};

  my $cpu_vms     = 0;

  foreach my $vm (sort(keys(%vmlist)))
  {
    print "=======================================\n";
    print "       VM/IP: $vm / $vmlist{$vm}->{'ip'}\n";
    print "          OS: $vmlist{$vm}->{'os'}\n";
    print "Display Name: $vmlist{$vm}->{'displayname'}\n";
    printf "  Powered On: %s\n",  $vmlist{$vm}->{'powered'} ? "TRUE" : "FALSE";
    printf "      Uptime: %s\n",  $vmlist{$vm}->{'strUptime'};
    print "    VM State: $vmlist{$vm}->{'state'}\n";
    printf "VMWare Tools: %s\n",  $vmlist{$vm}->{'toolslastactive'} ? "Active" : "Not Active";
    printf "   Heartbeat: %s\n",  $vmlist{$vm}->{'alive'} ? "TRUE" : "FALSE";
    print "   Sync Time: $vmlist{$vm}->{'synctime'}\n";
    print "      Server: $vmlist{$vm}->{'server'}\n";
    print " Config File: $vmlist{$vm}->{'cfgfile'}\n";
    print "  # of disks: $vmlist{$vm}->{'disks'}\n";

    #
    # Hash of hashes - fscking hate perl.  It's crap
    # like this that makes me bald :)
    #
    foreach my $disk (keys %{$vmlist{$vm}->{'disk'}} )
    {
      printf "%12s: Name: %s, Present: %s\n", $disk, %{$vmlist{$vm}->{'disk'}}->{$disk}->{'device'} .
                ":" . %{$vmlist{$vm}->{'disk'}}->{$disk}->{'filename'},
                %{$vmlist{$vm}->{'disk'}}->{$disk}->{'present'};
    }

    print "      # CPUs: $vmlist{$vm}->{'vcpuinfo'}->{'cpus'}\n";
    for ( my $c = 0; $c < $vmlist{$vm}->{'vcpuinfo'}->{'cpus'}; $c++ )
    {
      printf "#%-2d   VCPUID: %d\n", $c, $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'vcpuid'};
      printf "#%-2d    %%USED: %s\n", $c, $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'used'};
      $cpu_vms += $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'used'};
      printf "#%-2d   %%EUSED: %s\n", $c, $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'eused'};
      printf "#%-2d   %%READY: %s\n", $c, $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'ready'};
      printf "#%-2d     %%MEM: %s\n", $c, $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'mem'};
    }

    print "   Total RAM: $vmlist{$vm}->{'vcpuinfo'}->{'ram'}\n";
    print "     Private: $vmlist{$vm}->{'vcpuinfo'}->{'private'}\n";
    print "      Shared: $vmlist{$vm}->{'vcpuinfo'}->{'shared'}\n";
    print "        Free: $vmlist{$vm}->{'vcpuinfo'}->{'free'}\n";
    print "     Swapped: $vmlist{$vm}->{'vcpuinfo'}->{'swapped'}\n";
    print "     Balloon: $vmlist{$vm}->{'vcpuinfo'}->{'balloon'}\n";
    print "      Active: $vmlist{$vm}->{'vcpuinfo'}->{'active'}\n";

    print "=======================================\n\n";
  }
 
  print "Tot Used: $cpu_vms\n\n";
}

#
#  Call and access by reference
#
sub set_us_up_the_graph
{
  my ($green, $orange, $red, $white) = @_;

  debug_print 8, "Graph #1 [$$green][$$orange][$$red][$$white][" . ($$green + $$orange + $$red + $$white) . "]\n";

  if ( $$green > 60 )
  {
    $$orange    = $$green - 60;
    $$green     = 60;

    if ( $$orange > 30 )
    {
      $$red     = $$orange - 30;
      $$orange  = 30;
    }
  }

  $$white = PERCENT - ($$green + $$orange + $$red);

  debug_print 8, "Graph #2 [$$green][$$orange][$$red][$$white]" . ($$green + $$orange + $$red + $$white) . "]\n";
}

sub prepare_html_attachment
{
  my ($server)    = shift;

  my %vmlist      = %{$serverinfo{$server}->{'vmlist'}};

  my $tot_ram     = 0;
  my $tot_private = 0;
  my $tot_shared  = 0;
  my $tot_swap    = 0;
  my $tot_balloon = 0;
  my $tot_free    = 0;

  #
  # Total CPU usage
  #
  my $cpu_vms = 0;

  #
  # Buffer the output so we can create a more dynamic webpage.
  #
  my @vm_buf = ();

  #
  # Loop through each Virtual Machine and display its vital
  # statistics in HTML format.
  #
  foreach my $vm (sort(keys(%vmlist)))
  {
    my ($pct_private,$pct_shared,$pct_swap,$pct_balloon,$pct_free) = (0,0,0,0,0);

    my $present = 0;
    foreach my $disk (keys %{$vmlist{$vm}->{'disk'}} )
    {
      if ( %{$vmlist{$vm}->{'disk'}}->{$disk}->{'present'} eq "TRUE" )
      {
        $present++;
      }
    }

    #
    # Run some health checks only if powered on
    #
    my $status    = "ok";
    if ( $vmlist{$vm}->{'powered'} )
    {
      if ( $vmlist{$vm}->{'uptime'} < 86400 )
      {
        $status = "warn";
        $vmlist{$vm}->{'strUptime'} = "<span id=\"warn\">" . $vmlist{$vm}->{'strUptime'} . "</span>";
      } 

      if ( $vmlist{$vm}->{'state'} ne "on" )
      {
        $status = "warn";
        $vmlist{$vm}->{'strstate'} = "<span id=\"alert\">" . $vmlist{$vm}->{'state'} . "</span>";

        $warnings{$vmlist{$vm}->{'displayname'}}->{'state'} = $vmlist{$vm}->{'state'};
      }

      if ( ! $vmlist{$vm}->{'alive'} )
      {
        $status = "warn";
        $vmlist{$vm}->{'strheartbeat'} = "<span id=\"alert\">FALSE</span>";

        $warnings{$vmlist{$vm}->{'displayname'}}->{'heartbeat'} = 0;
      }

      if ( $vmlist{$vm}->{'synctime'} eq "FALSE" )
      {
        if ( $vmlist{$vm}->{'displayname'} !~ /DOM/ )
        {
          $status = "warn";
          $vmlist{$vm}->{'strsynctime'} = "<span id=\"alert\">FALSE</span>";

          $warnings{$vmlist{$vm}->{'displayname'}}->{'tsyncerr'} = "Server does not have sync time configured";
        }
      }
      elsif ( $vmlist{$vm}->{'displayname'} =~ /DOM/ )
      {
        $status = "warn";
        $vmlist{$vm}->{'strsynctime'} = "<span id=\"alert\">TRUE</span>";

        $warnings{$vmlist{$vm}->{'displayname'}}->{'tsyncerr'} = "Domain controller has sync time configured";
      }

      if ( ! $vmlist{$vm}->{'toolslastactive'} )
      {
        $status = "warn";
        $vmlist{$vm}->{'strtoolslastactive'} = "<span id=\"alert\">Not Active</span>";

        $warnings{$vmlist{$vm}->{'displayname'}}->{'vmtools'} = "VM Tools are not installed or not active";
      }
    }

    #
    # Begin the VM HTML output
    # (display the server name and the table headers)
    #
    my $status_line = (push (@vm_buf, sprintf $vm_begin, $vmlist{$vm}->{'powered'} ? "on" : "off",
          $vmlist{$vm}->{'displayname'},
          $vmlist{$vm}->{'os'}, $vmlist{$vm}->{'powered'} ? "On" : "Off",
          $vmlist{$vm}->{'vcpuinfo'}->{'cpus'}, $vmlist{$vm}->{'disks'}, $present,
          $vmlist{$vm}->{'strUptime'},
          pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'ram'})) - 1);

    #
    # Avoid divide by zero.
    #
    if ( defined $vmlist{$vm}->{'vcpuinfo'}->{'ram'} )
    {
      $tot_ram     += $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
      $tot_private += $vmlist{$vm}->{'vcpuinfo'}->{'private'};
      $tot_shared  += $vmlist{$vm}->{'vcpuinfo'}->{'shared'};
      $tot_swap    += $vmlist{$vm}->{'vcpuinfo'}->{'swapped'};
      $tot_balloon += $vmlist{$vm}->{'vcpuinfo'}->{'balloon'};
      $tot_free    += $vmlist{$vm}->{'vcpuinfo'}->{'free'};

      $pct_private = $vmlist{$vm}->{'vcpuinfo'}->{'private'} * PERCENT / $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
      $pct_shared  = $vmlist{$vm}->{'vcpuinfo'}->{'shared'}  * PERCENT / $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
      $pct_swap    = $vmlist{$vm}->{'vcpuinfo'}->{'swapped'} * PERCENT / $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
      $pct_balloon = $vmlist{$vm}->{'vcpuinfo'}->{'balloon'} * PERCENT / $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
      $pct_free    = $vmlist{$vm}->{'vcpuinfo'}->{'free'}    * PERCENT / $vmlist{$vm}->{'vcpuinfo'}->{'ram'};
    }

    debug_print 8, "\$vmlist{\$vm}->{'vcpuinfo'}->{'swapped'} = $vmlist{$vm}->{'vcpuinfo'}->{'swapped'}\n";
    debug_print 8, "private: $pct_private, shared: $pct_shared, swap: $pct_swap, balloon: $pct_balloon, free: $pct_free\n";

    my $swap_stat = "";
    my $ball_stat = "";

    #
    # Make sure we display a minimum of 1% for items with less then 1%
    #
    if ( $pct_swap > 0 )
    {
      $swap_stat = "background: red;";
      $status    = "warn";

      $warnings{$vmlist{$vm}->{'displayname'}}->{'pctswap'} = $pct_swap;

      if ( $pct_swap < 1 )
      {
        $pct_swap = 1;
      }
    }

    if ( $pct_balloon > 0 )
    {
      $ball_stat = "background: red;";
      $status    = "warn";

      if ( $pct_balloon < 1 )
      {
        $pct_balloon = 1;
      }
    }

    if ( $pct_free > 0 && $pct_free < 1 )
    {
      $pct_free = 1;
    }

    debug_print 8, "private: $pct_private, shared: $pct_shared, swap: $pct_swap, balloon: $pct_balloon, free: $pct_free\n";

    push (@vm_buf, sprintf $vm_graph, $pct_private, $pct_shared, $pct_swap, $pct_balloon, $pct_free);

    push (@vm_buf, sprintf $vm_memory, pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'active'}),
           pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'private'}), pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'shared'}),
           $swap_stat, pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'swapped'}),
           $ball_stat, pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'balloon'}),
           pretty_print($vmlist{$vm}->{'vcpuinfo'}->{'free'}));

    #
    # Get the CPU & Memory usage percentages.
    #
    my $tot_cpu = 0;
    my $tot_mem = 0;

    for ( my $c = 0; $c < $vmlist{$vm}->{'vcpuinfo'}->{'cpus'}; $c++ )
    { 
      $cpu_vms  += $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'used'};
      $tot_cpu  += $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'used'};
      $tot_mem  += $vmlist{$vm}->{'vcpuinfo'}->{$c}->{'mem'};
    }

    #
    # Fancify the performance stuff.
    #
    # Note: need to check the multi-cpu graphs work correctly.
    #
    my ($cpu_2,$cpu_3,$cpu_4,$mem_2,$mem_3,$mem_4);
    my $cpu_1 = $tot_cpu;
    my $mem_1 = $tot_mem;

    set_us_up_the_graph(\$cpu_1,\$cpu_2,\$cpu_3,\$cpu_4);
    set_us_up_the_graph(\$mem_1,\$mem_2,\$mem_3,\$mem_4);

    my $cpu_stat = "";
    my $mem_stat = "";

    if ( $tot_cpu > 70 )
    {
      $cpu_stat = " style=\"background:red;\"";
      $status   = "warn";

      $warnings{$vmlist{$vm}->{'displayname'}}->{'pctcpu'} = $tot_cpu;
    }

    if ( $tot_mem > 70 )
    {
      $mem_stat = " style=\"background:red;\"";
      $status   = "warn";

      $warnings{$vmlist{$vm}->{'displayname'}}->{'pctmem'} = $tot_mem;
    }

    push (@vm_buf, sprintf $vm_details,
          (defined $vmlist{$vm}->{'strtoolslastactive'}) ? $vmlist{$vm}->{'strtoolslastactive'} : $vmlist{$vm}->{'toolslastactive'} ? "Active" : "Not Active",
          (defined $vmlist{$vm}->{'strstate'}) ? $vmlist{$vm}->{'strstate'} : $vmlist{$vm}->{'state'},
          (defined $vmlist{$vm}->{'strheartbeat'}) ? $vmlist{$vm}->{'strheartbeat'} : $vmlist{$vm}->{'alive'} ? "TRUE" : "FALSE",
          (defined $vmlist{$vm}->{'strsynctime'}) ? $vmlist{$vm}->{'strsynctime'} : $vmlist{$vm}->{'synctime'},
          $vmlist{$vm}->{'pid'}, $vmlist{$vm}->{'vmid'},
          $cpu_stat, $cpu_1, $cpu_2, $cpu_3, $cpu_4,
          $mem_stat, $mem_1, $mem_2, $mem_3, $mem_4,
          $vmlist{$vm}->{'server'}, $vmlist{$vm}->{'cfgfile'});

    #
    # Do this before the trimmings
    #
    ($vm_buf[$status_line] = $vm_buf[$status_line]) =~ s/%CHANGEME%/$status/;

    #
    # Show all VMs or just those with issues?
    #
    if ( !$fulllist )
    {
      my @tmp_buf = splice(@vm_buf, $status_line);

      if ( $status eq "warn" )
      {
        foreach my $tmpline ( @tmp_buf )
        {
          push ( @{$serverinfo{$server}->{'oob'}}, $tmpline );
        }
      }
    }
    elsif ( $status eq "warn" )
    {
      $warnings{$vmlist{$vm}->{'displayname'}}->{'server'} = $vmlist{$vm}->{'server'};
    }
  }

  #
  # Print the server container.
  #
  $html_buf .= sprintf $begin_server, $server;

  #
  # Display server totals for CPU and memory utilisation.
  #
  # Green / Orage / Red / White
  #
  my $pct_vms = ($cpu_vms * PERCENT) / (%{$serverinfo{$server}->{'cpulist'}}->{'physical'} * PERCENT);
  my $pct_console = ($serverinfo{$server}->{'cpu_console'} * PERCENT) / (%{$serverinfo{$server}->{'cpulist'}}->{'physical'} * PERCENT);
  my $pct_other = ($serverinfo{$server}->{'cpu_other'} * PERCENT) / (%{$serverinfo{$server}->{'cpulist'}}->{'physical'} * PERCENT);

  #
  # $meminfo{'totmem'} does not include the console memory
  #
  my $rtotmem     = $serverinfo{$server}->{'meminfo'}->{'totmem'} + $serverinfo{$server}->{'meminfo'}->{'console'};

  #
  # Calculate the memory used for virtualisation
  #
  my $virt        = $serverinfo{$server}->{'meminfo'}->{'totmem'} -
     ($tot_private+$serverinfo{$server}->{'meminfo'}->{'common'}+$serverinfo{$server}->{'meminfo'}->{'memfree'});

  my $vm_mem_util = $tot_private + $serverinfo{$server}->{'meminfo'}->{'common'};
  my $ot_mem_util = $serverinfo{$server}->{'meminfo'}->{'console'} + $virt;
  my $to_mem_util = $vm_mem_util + $ot_mem_util;
  my $pct_vm_mem  = ($vm_mem_util * PERCENT) / $rtotmem;
  my $pct_vm_oth  = ($ot_mem_util * PERCENT) / $rtotmem;

  #
  # Save values for easy access to provide warnings, later.
  #
  if ( ($pct_vms + $pct_console + $pct_other) > 80 )
  {
    $warnings{$server}->{'warncpu'} = $pct_vms + $pct_console + $pct_other;
  }

  if ( $serverinfo{$server}->{'meminfo'}->{'memfree'} < 1024000 )
  {
    $warnings{$server}->{'warnmem'} = $serverinfo{$server}->{'meminfo'}->{'memfree'};
  }

  if ( $tot_balloon > 0 )
  {
    $warnings{$server}->{'warnball'} = $tot_balloon;
  }

  if ( $tot_swap > 0 )
  {
    $warnings{$server}->{'warnswap'} = $tot_swap;
  }

  #
  # % CPU utilisation
  #
  my ($cpu_vm1,$cpu_vm2,$cpu_vm3,$cpu_vm4) = ($pct_vms,0,0,0);
  my ($cpu_ot1,$cpu_ot2,$cpu_ot3,$cpu_ot4) = ($pct_console + $pct_other,0,0,0);
  my ($cpu_to1,$cpu_to2,$cpu_to3,$cpu_to4) = (($pct_vms + $pct_console + $pct_other),0,0,0);

  #
  # Struggling for function names here...
  #
  set_us_up_the_graph(\$cpu_vm1, \$cpu_vm2, \$cpu_vm3, \$cpu_vm4);
  set_us_up_the_graph(\$cpu_ot1, \$cpu_ot2, \$cpu_ot3, \$cpu_ot4);
  set_us_up_the_graph(\$cpu_to1, \$cpu_to2, \$cpu_to3, \$cpu_to4);

  #
  # % Memory utilisation
  #
  my ($mem_vm1,$mem_vm2,$mem_vm3,$mem_vm4) = ($pct_vm_mem,0,0,0);
  my ($mem_ot1,$mem_ot2,$mem_ot3,$mem_ot4) = ($pct_vm_oth,0,0,0);
  my ($mem_to1,$mem_to2,$mem_to3,$mem_to4) = (($pct_vm_mem + $pct_vm_oth),0,0,0);

  #
  # Prettify the percentages
  #
  set_us_up_the_graph(\$mem_vm1, \$mem_vm2, \$mem_vm3, \$mem_vm4);
  set_us_up_the_graph(\$mem_ot1, \$mem_ot2, \$mem_ot3, \$mem_ot4);
  set_us_up_the_graph(\$mem_to1, \$mem_to2, \$mem_to3, \$mem_to4);

  #
  # Display the CPU/Mem Utilisation bars
  #
  $html_buf .= sprintf $vm_util, "info",
               $cpu_vm1, $cpu_vm2, $cpu_vm3, $cpu_vm4, $pct_vms,
               $mem_vm1, $mem_vm2, $mem_vm3, $mem_vm4, pretty_print($vm_mem_util),
               $cpu_ot1, $cpu_ot2, $cpu_ot3, $cpu_ot4, ($pct_console + $pct_other),
               $mem_ot1, $mem_ot2, $mem_ot3, $mem_ot4, pretty_print($ot_mem_util),
               $cpu_to1, $cpu_to2, $cpu_to3, $cpu_to4, ($pct_vms + $pct_console + $pct_other),
               $mem_to1, $mem_to2, $mem_to3, $mem_to4, pretty_print($to_mem_util);

  #
  # Calculate the physical memory percentages to be displayed in a graph
  #
  my $pct_vm  = ($vm_mem_util * PERCENT) / $rtotmem;
  my $pct_sh  = ($serverinfo{$server}->{'meminfo'}->{'common'} * PERCENT) / $rtotmem;
  my $pct_vi  = ($virt * PERCENT) / $rtotmem;
  my $pct_co  = ($serverinfo{$server}->{'meminfo'}->{'console'} * PERCENT) / $rtotmem;
  my $pct_fr  = ($serverinfo{$server}->{'meminfo'}->{'memfree'} * PERCENT) / $rtotmem;

  #
  # Display the physical memory percentages as MB/GB/etc.
  #
  $html_buf .= sprintf $vm_phys_mem,
               $pct_vm, $pct_sh, $pct_vi, $pct_co, $pct_fr,
               pretty_print($tot_private), pretty_print($serverinfo{$server}->{'meminfo'}->{'common'}),
               pretty_print($virt), pretty_print($serverinfo{$server}->{'meminfo'}->{'console'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'memfree'}), pretty_print($rtotmem),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'shared'} - $serverinfo{$server}->{'meminfo'}->{'common'});

  my $tot_res_mem    = $serverinfo{$server}->{'meminfo'}->{'totresmem'};
  my $pct_resmem     = ($serverinfo{$server}->{'meminfo'}->{'totresmem'} - $serverinfo{$server}->{'meminfo'}->{'unresmem'}) * PERCENT / $tot_res_mem;
  my $pct_unresmem   = $serverinfo{$server}->{'meminfo'}->{'unresmem'}  * PERCENT / $tot_res_mem;

  my $tot_res_mem    = $serverinfo{$server}->{'meminfo'}->{'totresswap'};
  my $pct_swapres    = ($serverinfo{$server}->{'meminfo'}->{'totresswap'} - $serverinfo{$server}->{'meminfo'}->{'unresswap'}) * PERCENT / $tot_res_mem;
  my $pct_unresswap  = $serverinfo{$server}->{'meminfo'}->{'unresswap'} * PERCENT / $tot_res_mem;

  $html_buf .= sprintf $vm_res_mem,
               $pct_resmem, $pct_unresmem, (PERCENT - ($pct_resmem + $pct_unresmem)),
               $pct_swapres, $pct_unresswap, (PERCENT - ($pct_swapres + $pct_unresswap)),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'totresmem'} - $serverinfo{$server}->{'meminfo'}->{'unresmem'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'unresmem'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'totresmem'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'totresswap'} - $serverinfo{$server}->{'meminfo'}->{'unresswap'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'unresswap'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'totresswap'}),
               pretty_print($serverinfo{$server}->{'meminfo'}->{'newcpu1'}), pretty_print($serverinfo{$server}->{'meminfo'}->{'newcpu2'});

  debug_print 7, "TOTAL: tot_private: $tot_private, tot_shared: $tot_shared, tot_swap: $tot_swap, tot_balloon: $tot_balloon, tot_free: $tot_free\n";

  #
  # I would hope this is always > 0
  #
  my ($pct_private,$pct_shared,$pct_swap,$pct_balloon,$pct_free) = (0,0,0,0,0);
  if ( $tot_ram > 0 )
  {
    $pct_private = $tot_private * PERCENT / $tot_ram;
    $pct_shared  = $tot_shared  * PERCENT / $tot_ram;
    $pct_swap    = $tot_swap    * PERCENT / $tot_ram;
    $pct_balloon = $tot_balloon * PERCENT / $tot_ram;
    $pct_free    = $tot_free    * PERCENT / $tot_ram;
  }

  debug_print 7, "TOTAL: pct_private: $pct_private, pct_shared: $pct_shared, pct_swap: $pct_swap, pct_balloon: $pct_balloon, pct_free: $pct_free\n";

  $html_buf .= sprintf $vm_mem_usage, $serverinfo{$server}->{'vms'}, $pct_private, $pct_shared, $pct_swap, $pct_balloon, $pct_free,
               pretty_print($tot_private), pretty_print($tot_shared), pretty_print($tot_swap),
               pretty_print($tot_balloon), pretty_print($tot_free), pretty_print($tot_ram);

  #
  # Print the VM information.
  #
  foreach my $line (@vm_buf)
  {
    $html_buf .= $line;
  }

  #
  # Close the server container.
  #
  $html_buf .= "<!-- Close the server container -->\n</div>\n";
}

sub get_mpreport
{
  my $server        = shift;
  my $host          = get_server($server);
  my $disk          = "";
  my $path          = 0;

  vprint "Fetching multipath report\n";

  my $cmd = "${sshcmd} $host -x '/usr/local/svvs/bin/sudo /usr/sbin/vmkmultipath -q' |";

  open INP, $cmd;
  while (<INP>)
  {
    if ( /Disk (.*) \(.*\) has (\d+) paths.\s+Policy is (\S+)./ )
    {
      $disk = $1;
      %{$mpreport{$server}{$disk}}->{'paths'}  = $2;
      %{$mpreport{$server}{$disk}}->{'policy'} = $3;
      $path = 0;
    }
    elsif ( /(\S+:\d+:\d+)\s+(\S+)/ )
    {
      next if /only 1 path/;

      %{$mpreport{$server}{$disk}}->{$path}->{'name'}  = $1;
      %{$mpreport{$server}{$disk}}->{$path}->{'state'} = $2;

      if ( /active/ )
      {
        %{$mpreport{$server}{$disk}}->{$path}->{'active'} = 1;
      }

      if ( /preferred/ )
      {
        %{$mpreport{$server}{$disk}}->{$path}->{'prefer'} = 1;
      }

      $path++;
    }
  }
}

sub parse_server
{
  my $server        = shift;
  debug_print 1, "\$server = [$server]\n";

  my %vmlist        = ();

  my $vms           = 0;  #
  my $powered       = 0;  #
  my $vcpus         = 0;  #
  my %tmp_vcpu_list = (); # Temporary variables
  my %vcpuid_list   = ();
  my %nonvmm_list   = ();
  my $pass          = 0;
  my $host          = get_server($server);

  if ( defined $dev_servers{$server} )
  {
    $serverinfo{$server}->{'devel'} = 1;
    $tot_dev++;
  }
  else
  {
    $serverinfo{$server}->{'devel'} = 0;
    $tot_pro++;
  }

  # Fetch the server version so we know which commands/checks to run
  $serverinfo{$server}->{'version'} = "na";
  vprint "Fetching the ESX Server Version: ";
  my $cmd = "${sshcmd} $server -x 'vmware -v' |";
  open INP, $cmd;
  while (<INP>)
  {
    if ( /VMware\s+ESX\s+Server\s+(\d+)/ )
    {
      $serverinfo{$server}->{'version'} = $1;
    }
  }
  close(INP);
  vprint $serverinfo{$server}->{'version'} . "\n";

  vprint "Fetching the list of Virtual Machines on $server\n";
  #
  # Get the list of Virtual Machines which are under the control
  # of this server.
  #
  my $cmd = "${sshcmd} $host -x 'vmware-cmd -l' |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    chomp();

    # Looking for: /home/vmware/LNVMIAPP07U/LNVMIAPP07U.vmx
    #          or: /home/vmware/Clone of LNVMIWFL71D_1/Clone of LNVMIWFL71D_1.vmx
    # (This is for finding which VMs are running on which server)

    if ( /home\/vmware\/(.*)\/(.*).vmx/ )
    {
      my $vm = $1;

      debug_print 5, sprintf "%-34s : %s\n", $vm, $_;

      $vmlist{$vm}->{'cfgfile'}         = $_;
      $vmlist{$vm}->{'server'}          = $server;
      $vmlist{$vm}->{'ip'}              = get_server($server);

      $vmlist{$vm}->{'powered'}         = 0;
      $vmlist{$vm}->{'disks'}           = 0;

      $vmlist{$vm}->{'state'}           = "na";
      $vmlist{$vm}->{'uptime'}          = 0;
      $vmlist{$vm}->{'heartbeat'}       = 0;
      $vmlist{$vm}->{'alive'}           = 0;
      $vmlist{$vm}->{'toolslastactive'} = 0;

      $vms++;
    }
  }
  close(INP);

  vprint "Fetching the config for each of the Virtual Machines on $server\n";
  #
  # Get the config of each Virtual Machine.
  # (Also fetch the heartbeat and the VM state)
  #
  my $c = 1;
  foreach my $vm (keys %vmlist)
  {
    vprint progress_bar( $c, $vms );

    #
    # The 'grep -q' improves speed significantly by not running the 
    # vmware-cmd's on VMs which are not running.
    #
    # Call getheartbeat twice.  One at the beginning and one at the end, which,
    # if the VM is alive and has a hearbeat, will contain different values.
    #

    my $cmd = "${sshcmd} $host -x \"cat \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\";" . 
                        "grep -q \\\"$vm\\\" /proc/vmware/vm/*/names && " .
                        "vmware-cmd \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\" getheartbeat;" .
                        "vmware-cmd \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\" getstate;" .
                        "vmware-cmd \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\" gettoolslastactive;" .
                        "vmware-cmd \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\" getuptime;sleep 2;" .
                        "vmware-cmd \\\"" . $vmlist{$vm}->{'cfgfile'} . "\\\" getheartbeat \"|";
    debug_print 2, $cmd . "\n";

    open INP, $cmd;
    while(<INP>)
    {
      chomp();

      if ( /scsi(\d+:\d+)\.name = "(.*):(.*)"/i )
      {
        my ($scsi_id, $device, $filename) = ($1, $2, $3);

        $vmlist{$vm}->{'disks'}++;
        %{$vmlist{$vm}->{'disk'}->{$scsi_id}}->{'device'} = $device;
        %{$vmlist{$vm}->{'disk'}->{$scsi_id}}->{'filename'} = $filename;
      }
      elsif ( /scsi(\d+:\d+).present = "(\S+)"/i )
      {
        my ($scsi_id, $value) = ($1, $2);

        %{$vmlist{$vm}->{'disk'}->{$scsi_id}}->{'present'} = $value;
      }
      elsif ( /guestOS = "(.*)"/i )
      {
        $vmlist{$vm}->{'os'} = $1;
      }
      elsif ( /tools.synctime = "(.*)"/i )
      {
        $vmlist{$vm}->{'synctime'} = $1;
      }
      elsif ( /displayName = "(.*)"/i )
      {
        $vmlist{$vm}->{'displayname'} = $1;
      }
      elsif ( /^getstate\(\) = (\S+)/ )
      {
        $vmlist{$vm}->{'state'} = $1;
      }
      elsif ( /^gettoolslastactive\(\) = (\d+)/ )
      {
        $vmlist{$vm}->{'toolslastactive'} = $1;
      }
      elsif ( /^getuptime\(\) = (\d+)/ )
      {
        $vmlist{$vm}->{'uptime'} = $1;
        my @parts = gmtime($1);
        $vmlist{$vm}->{'strUptime'} = sprintf "%d Days, %d Hours, %d Mins", @parts[7,2,1];
      }
      elsif ( /^getheartbeat\(\) = (\d+)/ )
      {
        my $heartbeat = $1;

        # As this gets called twice, the stored value should never be the
        # same as the current value on the last pass, if the VM is alive.
        if ( $vmlist{$vm}->{'heartbeat'} == $heartbeat )
        {
          $vmlist{$vm}->{'alive'} = 0;
        }
        else
        {
          $vmlist{$vm}->{'alive'} = 1;
        }

        $vmlist{$vm}->{'heartbeat'} = $heartbeat;
      }
    }
    $c++;
  }

  vprint "\nFetching CPU information for VMs running on $server [$host]\n";
  #
  # esxtop replacement, as esxtop likes to misbehave
  # Polls cpu status file with a 5 sec interlude, and uses the difference
  # to calculate the current CPU percentages.
  # Code needs a clean up, but this will do for 
  #
  my $cmd = "${sshcmd} $host -x 'cat /proc/vmware/vm/*/cpu/status\; echo EOF\; sleep 5\; cat /proc/vmware/vm/*/cpu/status' |";
  debug_print 1, $cmd . "\n";

  $serverinfo{$server}->{'cpu_console'} = 0;
  $serverinfo{$server}->{'cpu_other'}   = 0;

  open INP, $cmd;
  while (<INP>)
  {
    chomp();

    s/^\s+//;
    
    # split up data, increment pass counter and filter ${sshcmd} noise
    my @line = split(/\s+/);
    if ($line[0] =~ /EOF/) { $pass++; }
    if ($#line != 20) { next; }

    # Ignore lines containing vcpu and idle
    my ($vcpuid, $wid, $wtype, $uptime, $usedsec, $readysec) 
       = @line[0,1,3,4,7,12];
    if ($vcpuid =~ /vcpu/ || $wtype =~ /idle/) { next; }

    # If this is the first pass...
    if ( $pass == 0 )
    {   
        # and it's a VM...
        if ( $wtype =~ /vmm/ )
        {   
            # If this is the first instance, create a new entry
            if ( not defined $vcpuid_list{$wid}->{'cpus'} )
            {   
                $vcpuid_list{$wid}->{'cpus'} = 0;
            }
            
            my $cpu = $vcpuid_list{$wid}->{'cpus'}++;
            $vcpuid_list{$wid}->{$cpu}->{'vcpuid'}   = $vcpuid;
            $vcpuid_list{$wid}->{$cpu}->{'wid'}      = $wid;
            
            ###
            # to make calculations easier in the long run, we shall
            # use negative values and then add the second value,
            # giving us a positive difference value, e.g.
            # -300.30 + 304.30 = 4
            # Therefore the cpu was used for 4 secs out of 5 = 80%
            ###
            $vcpuid_list{$wid}->{$cpu}->{'uptime'}   -= $uptime;
            $vcpuid_list{$wid}->{$cpu}->{'usedsec'}  -= $usedsec;
            $vcpuid_list{$wid}->{$cpu}->{'readysec'} -= $readysec;
            
            if ( not defined $tmp_vcpu_list{$wid} )
            {   
                $tmp_vcpu_list{$wid} = $wid;
                $vcpus++;
            }
        }
        # or it's the console...
        elsif ( $wtype =~ /console/ )
        {   
            $nonvmm_list{'console'}->{$wid}->{'vcpuid'}   = $vcpuid;
            $nonvmm_list{'console'}->{$wid}->{'wid'}      = $wid;
            $nonvmm_list{'console'}->{$wid}->{'uptime'}   -= $uptime;
            $nonvmm_list{'console'}->{$wid}->{'usedsec'}  -= $usedsec;
            $nonvmm_list{'console'}->{$wid}->{'readysec'} -= $readysec;
        }
        # or anything else on the system
        else
        {   
            $nonvmm_list{'other'}->{$wid}->{'vcpuid'}   = $vcpuid;
            $nonvmm_list{'other'}->{$wid}->{'wid'}      = $wid;
            $nonvmm_list{'other'}->{$wid}->{'uptime'}   -= $uptime;
            $nonvmm_list{'other'}->{$wid}->{'usedsec'}  -= $usedsec;
            $nonvmm_list{'other'}->{$wid}->{'readysec'} -= $readysec;
        }
    }
    else
    {   
        # Onto the second pass
        if ( $wtype =~ /vmm/ )
        {   
            # This time find the correct entry and get the difference
            foreach ( my $c = 0; $c < $vcpuid_list{$wid}->{'cpus'}; $c++ )
            {   
                if ($vcpuid_list{$wid}->{$c}->{'vcpuid'} == $vcpuid)
                {   
                   # We'll set this later
                   $vcpuid_list{$wid}->{$c}->{'mem'} = 0;
                    
                    # Adding 2nd value to negative 1st value = difference
                    $vcpuid_list{$wid}->{$c}->{'uptime'}   += $uptime;
                    $vcpuid_list{$wid}->{$c}->{'usedsec'}  += $usedsec;
                    $vcpuid_list{$wid}->{$c}->{'readysec'} += $readysec;

                   # used time / total time * 100 = used%
                    $vcpuid_list{$wid}->{$c}->{'used'} =
                        $vcpuid_list{$wid}->{$c}->{'usedsec'}
                        / $vcpuid_list{$wid}->{$c}->{'uptime'}
                        * 100;
                    $vcpuid_list{$wid}->{$c}->{'ready'} =
                        $vcpuid_list{$wid}->{$c}->{'readysec'}
                        / $vcpuid_list{$wid}->{$c}->{'uptime'}
                        * 100;

                    # printf("VMM: %d - %d - used: %.2f%%, ready: %.2f%%\n",
                    #    $vcpuid_list{$wid}->{$c}->{'vcpuid'},
                    #    $vcpuid_list{$wid}->{$c}->{'wid'},
                    #    $vcpuid_list{$wid}->{$c}->{'used'},
                    #    $vcpuid_list{$wid}->{$c}->{'ready'});
                }
            }
        }
        elsif ( $wtype =~ /console/ )
        {
            # Adding 2nd value to negative 1st value = difference
            $nonvmm_list{'console'}->{$wid}->{'uptime'}   += $uptime;
            $nonvmm_list{'console'}->{$wid}->{'usedsec'}  += $usedsec;
            $nonvmm_list{'console'}->{$wid}->{'readysec'} += $readysec;

            # used time / total time * 100 = used%
            $serverinfo{$server}->{'cpu_console'} +=
                $nonvmm_list{'console'}->{$wid}->{'usedsec'}
                / $nonvmm_list{'console'}->{$wid}->{'uptime'}
                * 100;
           # printf("Console: %d - used: %.2f%%\n", 
           #           $wid, $serverinfo{$server}->{'cpu_console'});
        }
        else
        {
            # Adding 2nd value to negative 1st value = difference
            $nonvmm_list{'other'}->{$wid}->{'uptime'}   += $uptime;
            $nonvmm_list{'other'}->{$wid}->{'usedsec'}  += $usedsec;
            $nonvmm_list{'other'}->{$wid}->{'readysec'} += $readysec;

            # used time / total time * 100 = used%
            $serverinfo{$server}->{'cpu_other'} += 
               $nonvmm_list{'other'}->{$wid}->{'usedsec'} 
               / $nonvmm_list{'other'}->{$wid}->{'uptime'} 
               * 100;
           # printf("Other: %d - used: %.2f%%\n", 
           #           $wid, $serverinfo{$server}->{'cpu_other'});
       }
     }
  }

  vprint "Fetching Memory information for VMs running on $server [$host]\n";
  my $cmd =  "${sshcmd} $host -x 'cat /proc/vmware/vm/*/mem/status' |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    chomp();
    
    s/^\s+//;
    
    my @line = split(/\s+/);

    my ($wid, $max, $active) = @line[0,4,16];
    if ($wid =~ /vm/) { next; }

    foreach ( my $c = 0; $c < $vcpuid_list{$wid}->{'cpus'}; $c++ )
    {   
        $vcpuid_list{$wid}->{$c}->{'mem'} = $active / $max * 100;
    }
  }

  my $cmd = "${sshcmd} $host -x 'free -k' |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    chomp();

    # total/used/free/shared/buffers/cached
    if ( /^Mem:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/ )
    {
      $serverinfo{$server}->{'meminfo'}->{'console'} = $1;
    }
  }

  vprint "Fetching Extended memory information for VMs running on $server\n";
  #
  # NOTE: Need to change this to /proc/vmware/sched/mem
  #
  # Sneaking in "cat /proc/vmware/sched/ncpus" to cut down on the
  # SSH oonnection overhead.
  # Sneaking in "cat /proc/vmware/mem", too.
  # And "cat /proc/vmware/NUMA/hardware".
  #
  my $cmd = "${sshcmd} $host -x 'cat /proc/vmware/sched/mem-verbose /proc/vmware/sched/ncpus /proc/vmware/mem' |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    chomp();

    #1 vm mctl? shares min max size/sizetgt memctl/mctltgt swapped/swaptgt
    #2 swapin swapout cptread/cpt-tgt shared active overhd/ovhdmax ovhd peak affinity | mintgt
    #3 adjmin amin? rspd? target mctlmax cow zero xshared  hint
    #4 est/slo/fst/nxt charged cowSwapped  mpps
    if ( /(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\/\s*(\d+)\s+(\d+)\/\s*(\d+)\s+(\d+)\/\s*(\d+)(.*)/ )
    {
      #
      # Split into several lines for clarity :)
      #
      my ($vmid,$mctl,$shares,$min,$max,$size,$sztgt,$memctl,$mctltgt,$swapped,$swaptgt,$rest) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);

      $_ = $rest;
      /(\d+)\s+(\d+)\s+(\d+)\/\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\/\s*(\d+)\s+(\d+)\s+(.*)\s+\|\s+(\d+)(.*)/;
      my ($swapin,$swapout,$cptread,$cpttgt,$shared,$active,$overhd,$ovhdmax,$ovhpeak,$affinity,$mintgt,$rest) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);

      $_ = $rest;
      /(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)(.*)/;
      my ($adjmin,$amin,$rspd,$target,$mctlmax,$cow,$zero,$xshared,$hint,$rest) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);

      $_ = $rest;
      /(\d+)\/\s*(\d+)\/\s*(\d+)\/\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/;
      my ($est,$slo,$fst,$nxt,$charged,$cowswapped,$mpps) = ($1,$2,$3,$4,$5,$6,$7);

      #
      # All that for this!?
      #
      # All values are in K (AFAIK)
      #
      $vcpuid_list{$vmid}->{'ram'}     = $max;
      $vcpuid_list{$vmid}->{'size'}    = $size;
      $vcpuid_list{$vmid}->{'shared'}  = $shared;
      $vcpuid_list{$vmid}->{'swapped'} = $swapped;
      $vcpuid_list{$vmid}->{'private'} = $size - $shared;
      $vcpuid_list{$vmid}->{'free'}    = $max - ( $size + $swapped + $memctl );
      $vcpuid_list{$vmid}->{'active'}  = $active;
      $vcpuid_list{$vmid}->{'balloon'} = $memctl;

      debug_print 9, "max: $max, size: $size, shared: $shared, active: $active, swapped: $swapped, balloon: $memctl, free: $vcpuid_list{$vmid}->{'free'}\n";
    }
    elsif ( /^\s*(\d+)\s+(\S+)\s*$/ )
    {
      # This captures <num> <type>
      my ($num, $type) = ($1, $2);

      debug_print 4, "Server: $server, Type: $type, Value: $num\n";
      %{$serverinfo{$server}->{'cpulist'}}->{$type} = $num;
    }
    elsif ( /Unreserved machine memory: (\d+) Mbytes\/(\d+) Mbytes/ )
    {
      $serverinfo{$server}->{'meminfo'}->{'unresmem'}   = $1 * 1024;
      $serverinfo{$server}->{'meminfo'}->{'totresmem'}  = $2 * 1024;
    }
    elsif ( /Unreserved swap space: (\d+) Mbytes\/(\d+) Mbytes/ )
    {
      $serverinfo{$server}->{'meminfo'}->{'unresswap'}  = $1 * 1024;
      $serverinfo{$server}->{'meminfo'}->{'totresswap'} = $2 * 1024;
    }
    elsif ( /Machine memory free: (\d+) Mbytes\/(\d+) Mbytes/ )
    {
      $serverinfo{$server}->{'meminfo'}->{'memfree'}    = $1 * 1024;
      $serverinfo{$server}->{'meminfo'}->{'totmem'}     = $2 * 1024;
    }
    elsif ( /Shared memory \(shared\/common\): (\d+) Kbytes\/(\d+) Kbytes/ )
    {
      $serverinfo{$server}->{'meminfo'}->{'shared'}     = $1;
      $serverinfo{$server}->{'meminfo'}->{'common'}     = $2;
    }
    elsif ( /Maximum new (\d+)-vcpu VM size: (\d+) Mbytes/ )
    {
      my $cpu = "newcpu" . $1;
      $serverinfo{$server}->{'meminfo'}->{$cpu}   = $2 * 1024;
    }
    #elsif ( // )
    #{
    #}
  }

  vprint "Matching VMs with Memory/CPU information\n";
  #
  # For each VM running, try and associate it with a config file
  #
  my $cmd = "${sshcmd} $host -x 'cat /proc/vmware/vm/*/names' |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    if ( /vmid=(\d+)\s+pid=(\d+)\s+cfgFile="(.*)"\s+uuid.*displayName="(.*)"/ )
    {
      my ($vmid,$pid,$cfgfile,$displayname) = ($1,$2,$3,$4);

      my $match = 0;

      foreach my $vm (keys %vmlist)
      { 
        if ( $vmlist{$vm}->{'cfgfile'} eq $cfgfile )
        {
          debug_print 1, sprintf "   Match: %-34s : %s\n", $vm, $cfgfile;

          $vmlist{$vm}->{'powered'}   = 1;
          $vmlist{$vm}->{'vmid'}      = $vmid;
          $vmlist{$vm}->{'pid'}       = $pid;
          $vmlist{$vm}->{'vcpuinfo'}  = $vcpuid_list{$vmid};

          $powered++;
          $match = 1;
          last;
        }
      }

      if ( $match == 0 )
      {
          debug_print 1, sprintf "No Match: %-34s : %s\n", $cfgfile;
      }
    }
  }

  # temp.
  my %events = ();

  vprint "Fetching events and tasks information\n";
  #
  # Fetch the event logs (tasks/events)
  #
  my $cmd = "${sshcmd} $host -x \"egrep -v '<event.*\\\\\\\"info\\\\\\\"' /var/log/vmware/\* \" |";
  debug_print 1, $cmd . "\n";

  open INP, $cmd;
  while (<INP>)
  {
    if ( /\/var\/log\/vmware\/event\-(.*\.vmx)\.log:(.*)/ )
    {
      #
      # Filename is HTML encoded!?
      #
      # Nice related page: http://users.easystreet.com/ovid/cgi_course/appendices/appendix2.html
      #
      my $cfgfile = uri_unescape($1);
      $_ = $2;

      debug_print 3, "[$cfgfile]\n";

      #
      # Cut down the # calls to the next loop by doing this bit first.
      #
      /<event type="(\S+)" id="(\d+)"><time>(\d+)<\/time><subject>(.*)<\/subject><body>(.*)<\/body>(.*)/;
      my ($event, $id, $time, $subject, $body) = ($1, $2, $3, $4, $5);

      if ( $time > time() - 186400 )
      {
        #debug_print 4, "[$event][$id][$time][" . time() . "][$subject][$body]\n";
      }
      debug_print 4, "[$event][$id][$time][" . time() . "][$subject][$body]\n";

      $events{$id}->{'subject'}    = $subject;

      #
      # Slow and painful...
      #
      foreach my $vm (keys %vmlist)
      { 
        if ( $vmlist{$vm}->{'cfgfile'} eq $cfgfile )
        { 
          last;
        }
      }
    }
  }

  $serverinfo{$server}->{'powered'}   = $powered;
  $serverinfo{$server}->{'vms'}       = $vms;
  %{$serverinfo{$server}->{'vmlist'}} = %vmlist;
}

sub get_server
{
  my $server = shift;

  if ( defined $dev_servers{$server} ) 
  {
    return $dev_servers{$server};
  }
  elsif ( defined $prod_servers{$server} )
  {
    return $prod_servers{$server};
  }

  return 0;
}

sub list_servers
{
  print "\nDevelopment servers\n";
  print "=" x 34 . "\n";
  foreach my $server (sort(keys(%dev_servers)))
  {
    print sprintf "%-20s %-20s\n", $server, $dev_servers{$server};
  }

  print "\nProduction servers\n";
  print "=" x 34 . "\n";
  foreach my $server (sort(keys(%prod_servers)))
  {
    print sprintf "%-20s %-20s\n", $server, $prod_servers{$server};
  }

  print "\n";
}

sub get_redo_files
{
  vprint "Checking for old and unused REDO files\n";

  foreach my $chkhost ( qw(lnhiesx18d lnhiesx01) )
  {
    my $cmd = "${sshcmd} " . get_server($chkhost) . " -x \"ls --time=ctime -l /vmfs/vmhba*/*.REDO\" |";
    debug_print 1, $cmd . "\n";

    open INP, $cmd;
    while (<INP>)
    {
      chomp();

      # Looking for: /vmfs/vmhba1:0:12:1/LNVISQL06D_1_1.vmdk.REDO
      #          or: /vmfs/vmhba1:0:13:1/Clone of LNVIMAN06U.vmdk.REDO
      #
      # or (using ls)
      # -rw-------    1 root     root     4546626048 Aug  8 02:28 /vmfs/vmhba1:0:9:1/LNVMIAPP74U.vmdk.REDO.REDO
      # -rw-------    1 root     root     218104320  Jan  4  2008 /vmfs/sanr509/LNVMIAPP74U.vmdk.REDO
      #

      #
      #                Month         time/year              filename
      #      file size   |     date      |             drive   |     redo label(s)
      #          |       |       |       |               |     |          |
      if ( /\s+(\d+)\s+(\S+)\s+(\d+)\s+(\S+)\s+\/vmfs\/(.*)\/(.*).vmdk(.REDO.*)/ )
      {
        my ($size, $mon, $day, $time, $drive, $name, $ext) = ($1, $2, $3, $4, $5, $6, $7);

        debug_print 8, "[$size][$drive][$name][$ext]\n";

        #
        # Because disk image files can be called something entirely different to
        # the "Display Name", we need to do a long winded search to match a redo
        # file with its virtual machine.
        #
        # So, for each host (or server) we are going to browse each VM and check
        # whether any of the disks belonging to that VM match the REDO file we
        # have found.
        #
        foreach my $server (sort(keys(%serverinfo)))
        {
          my %vmlist = %{$serverinfo{$server}->{'vmlist'}};

          foreach my $vm (sort(keys(%vmlist)))
          {
            my %disks = %{$vmlist{$vm}->{'disk'}};

            foreach my $disk (keys %disks)
            { 
              debug_print 8, "[$name" . ".vmdk][" . %{$disks{$disk}}->{'filename'} . "]\n";

              if ( $name . ".vmdk" eq %{$disks{$disk}}->{'filename'} )
              {
                #
                # Flag VM as having a large REDO file (for email body)
                #
                # Also flag REDO files which do not match todays date
                # (hopefully they get cleared within a month)
                #
                if ( $size > REDO_WARN || $day ne $gday )
                {
                  $warnings{$vmlist{$vm}->{'displayname'}}->{'redo'} = 1;
                  $warnings{$vmlist{$vm}->{'displayname'}}->{'server'} = $vmlist{$vm}->{'server'};
                }

                if ( $serverinfo{$server}->{'devel'} == 1 )
                {
                  $dredos{$name}->{'cdate'}   = $time . " " . $day . " " . $mon;
                  $dredos{$name}->{'size'}    = $size;
                  $dredos{$name}->{'disk'}    = $disk;
                  $dredos{$name}->{'ext'}     = $ext;
                  $dredos{$name}->{'server'}  = $server;
                  $dredos{$name}->{'devfile'} = %{$vmlist{$vm}->{'disk'}}->{$disk}->{'device'} .  ":" . %{$vmlist{$vm}->{'disk'}}->{$disk}->{'filename'};
                  $dredos{$name}->{'cfgfile'} = $vmlist{$vm}->{'cfgfile'};
                }
                else
                {
                  $predos{$name}->{'cdate'}   = $time . " " . $day . " " . $mon;
                  $predos{$name}->{'size'}    = $size;
                  $predos{$name}->{'disk'}    = $disk;
                  $predos{$name}->{'ext'}     = $ext;
                  $predos{$name}->{'server'}  = $server;
                  $predos{$name}->{'devfile'} = %{$vmlist{$vm}->{'disk'}}->{$disk}->{'device'} .  ":" . %{$vmlist{$vm}->{'disk'}}->{$disk}->{'filename'};
                  $predos{$name}->{'cfgfile'} = $vmlist{$vm}->{'cfgfile'};
                }
              }
            }
          }
        }
      }
    }
  }
}

sub get_disk_usage
{
  my $server = shift;

  my %vmlist = %{$serverinfo{$server}->{'vmlist'}};

  foreach my $vm (sort(keys(%vmlist)))
  {
    my %disks = %{$vmlist{$vm}->{'disk'}};

    debug_print 7, "get_disk_usage: \$vm=$vm\n";

    foreach my $disk (keys %disks)
    {
      #
      # Check it is a valid device
      #
      if ( $serverinfo{$server}->{'devel'} == 1 )
      {
        if ( (my $fs = $dsymlink{%{$disks{$disk}}->{'device'}}) )
        {
          $dluns{$fs}->{'vdisks'}++;
          %{$dluns{$fs}->{'vms'}{$vm}} = $vm;

          debug_print 8, "get_disk_usage: \$vm=$vm, \$fs=$fs, vdisks=" . $dluns{$fs}->{'vdisks'} . "\n";
        }
      }
      else
      {
        if ( (my $fs = $psymlink{%{$disks{$disk}}->{'device'}}) )
        {
          $pluns{$fs}->{'vdisks'}++;
          %{$pluns{$fs}->{'vms'}{$vm}} = $vm;

          debug_print 8, "get_disk_usage: \$vm=$vm, \$fs=$fs, vdisks=" . $pluns{$fs}->{'vdisks'} . "\n";
        }
      }
    }
  }
}

sub show_help
{
  print "\nSwitches:\n\n";
  print " -d <integer>        : Debug level\n";
  print " -et <email address> : Send output as HTML attachments to an email address using the XXXXXX hh.emea template (can be used multiple times)\n";
  print " -ep <email address> : Send output as HTML attachments to an email address using a plain email template (can be used multiple times)\n";
  print " -f                  : Full listing (show all VMs not just those which may have issues)\n";
  print " -h                  : Help (what you are reading now)\n";
  print " -l                  : List the servers which this script knows about\n";
  print " -s <server>         : Specify which servers to query (can be used multiple times)\n";
  print " -v                  : Be verbose (recommended for none cronjob runs)\n\n";

  print "Examples:\n\n esx_vmreport -v -et hh.emea\@xxxxxx.net, is sufficient for the daily checks\n";
  print " esx_vmreport -v -f -et hh.emea\@xxxxxx.net, will display all the VMs and not just those which may need attention\n";
  print " esx_vmreport -v -s lnhiesx23d -s lnhiesx26d, will only check lnhiesx23d and lnhiesx26d\n";
  print "\nNotes: If you do not request that the output be sent to an email address using the \"-et\" option,\n";
  print "       then all output will be sent to STDOUT in plain text\n\n";
}

my %server_errors = ();
sub error_report
{
  foreach my $vm (sort(keys(%warnings)))
  {
    my $strvm = "<font color=\"#009090\" size=\"3\"><b>$vm</b></font> - ";

    if ( get_server($vm) > 0 )
    {
      if ( defined($warnings{$vm}->{'warncpu'}) )
      {
        push (@{$server_errors{$vm}},
             sprintf "${strvm}<font color=\"#FF4000\" size=\"3\">Host CPU usage is: %d%%</font><br />\n\r",
             $warnings{$vm}->{'warncpu'});
      }

      if ( defined($warnings{$vm}->{'warnmem'}) )
      {
        push (@{$server_errors{$vm}}, 
             sprintf "${strvm}<font color=\"#FF4000\" size=\"3\">Host free memory is down to: %s</font><br />\n\r",
             pretty_print($warnings{$vm}->{'warnmem'}));
      }

      if ( defined($warnings{$vm}->{'warnball'}) )
      {
        push (@{$server_errors{$vm}}, 
             sprintf "${strvm}<font color=\"#FF4000\" size=\"3\">Host is ballooning: %s</font><br />\n\r",
             pretty_print($warnings{$vm}->{'warnball'}));
      }

      if ( defined($warnings{$vm}->{'warnswap'}) )
      {
        push (@{$server_errors{$vm}}, 
             sprintf "${strvm}<font color=\"#FF4000\" size=\"3\">Host is swapping: %s</font><br />\n\r",
             pretty_print($warnings{$vm}->{'warnswap'}));
      }
    }

    #
    # All these errors generally mean the box is running but the REDO file
    # is so large it has fucked the server somewhat.
    #
    if ( defined($warnings{$vm}->{'state'}) && defined($warnings{$vm}->{'heartbeat'}) &&
         defined($warnings{$vm}->{'vmtools'}) && defined($warnings{$vm}->{'redo'}) )
    {
      push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#FF4000\" size=\"3\">Needs to be rebooted to commit REDO file</font><br />\n\r");
    }
    elsif ( $warnings{$vm}->{'state'} eq "stuck" && defined($warnings{$vm}->{'heartbeat'}) )
    {
      push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#F09040\" size=\"3\">May need a question answering</font><br />\n\r");
    }
    else
    {
      if ( defined($warnings{$vm}->{'redo'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#6060A0\" size=\"3\">REDO file needs to be committed</font><br />\n\r");
      }

      if ( defined($warnings{$vm}->{'vmtools'}) && defined($warnings{$vm}->{'heartbeat'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#FF4000\" size=\"3\">May have crashed</font><br />\n\r");
      }
      elsif ( defined($warnings{$vm}->{'vmtools'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#F09040\" size=\"3\">VMWare Tools needs to be installed</font><br />\n\r");
      }
      elsif ( defined($warnings{$vm}->{'heartbeat'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#FF4000\" size=\"3\">Heartbeat failure - Crashed?</font><br />\n\r");
      }

      if ( defined($warnings{$vm}->{'tsyncerr'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, "${strvm}<font color=\"#F060A0\" size=\"3\">" . $warnings{$vm}->{'tsyncerr'} . "</font><br />\n\r");
      }

      if ( defined($warnings{$vm}->{'pctcpu'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, sprintf "${strvm}<font color=\"#6060A0\" size=\"3\">CPU usage is currently: %d%%</font><br />\n\r", $warnings{$vm}->{'pctcpu'});
      }

      if ( defined($warnings{$vm}->{'pctmem'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, sprintf "${strvm}<font color=\"#6060A0\" size=\"3\">Memory usage is currently: %d%%</font><br />\n\r", $warnings{$vm}->{'pctmem'});
      }

      if ( defined($warnings{$vm}->{'pctswap'}) )
      {
        push (@{$server_errors{$warnings{$vm}->{'server'}}}, sprintf "${strvm}<font color=\"#6060A0\" size=\"3\">Swap usage is currently: %d%%</font><br />\n\r",$warnings{$vm}->{'pctswap'});
      }
    }
  }
}

sub send_to_email
{ 
  my ($template, @email) = @_;

  debug_print 1, "Template: $template\n";

  #
  # Open Sendmail connection and setup the headers.
  #
  # Send the HTML as an RFC1341 compliant attachment as Outlook 2007
  # really hates to display HTML/CSS correctly.
  #
  open OUT, "| /usr/sbin/sendmail -t";
  print OUT "From: esx_vmreport\@uk6jump1.mgmt.xxxxxx.net\r\n";
  foreach my $address (@email)
  {
    print OUT "To: $address\r\n";
    debug_print 1, "Sending output via email: $address:\n";
  }
  print OUT "Subject: Performance Overview of X&X ESX Server Farm\r\n";
  print OUT "MIME-Version: 1.0\r\n";
  print OUT "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n\r\n";

  print OUT "This is a multi-part message in MIME format.\r\n\r\n";

  print OUT "\r\n--$boundary\r\n";
  print OUT "Content-Type: text/html\r\n\r\n";

  if ( $template == 1 )
  {
    print OUT "<p><font size=\"5\">This email requires a case to be created and assigned to a UNIX TAC Engineer.</font></p>\r\n";

    print OUT "<p><font size=\"5\">The email should be filed in \"ESX Daily Reports\" within the \"XXXXXXXXXXXXX\" folder.</font>\r\n";

    print OUT "<p><font size=\"4\">Below is a brief summary of possible issues. \
          This should not be considered a comprehensive list and should only be used as a guide. \
          Some high lighted issues may not be a problem, whilst others may be caused by an underlying issue.</font></p>\r\n<p>\r\n";
    foreach my $server (sort(keys(%server_errors)))
    {
      print OUT "<font color=\"#005090\" size=\"4\">Host:</font> <font color=\"#0090B0\" size=\"4\">$server</font><br />\r\n";

      foreach my $report (@{$server_errors{$server}})
      {
        print OUT $report;
      }

      print OUT "<br />\r\n";
    }
    print OUT "</p>\r\n";

    print OUT "<p><a href=\"http://lnddms/EMEA%20Hosting/Customer%20Key%20Documents/XXXXX%20and%20XXXXX%20(S259402)/Work%20Instructions/Operations/VMWare/ESX%20Daily%20Report%20Overview.doc\">How to analyse the ESX Overview report</a>.</p>\r\n";
 
    print OUT "<p><a href=\"http://lnddms/EMEA%20Hosting/Customer%20Key%20Documents/XXXXX%20and%20XXXXX%20(S259402)/Work%20Instructions/Operations/XXXXX%20and%20XXXXX%20-%20VMWare%20ESX%20Host%20and%20Guest%20Maintenance%20and%20Troubleshooting.doc\">VMWare ESX Host and Guest Maintenance and Troubleshooting</a></p>";
  }
  else
  {
    print OUT "<p><h3>Daily server farm report</h3></p>\r\n";

    print OUT "<p><font size=\"4\">Below is a brief summary of possible issues. \
          This should not be considered a comprehensive list and should only be used as a guide. \
          Some high lighted issues may not be a problem, whilst others may be caused by an underlying issue.</font></p>\r\n<p>\r\n";
    foreach my $server (sort(keys(%server_errors)))
    {
      print OUT "<font color=\"#005090\" size=\"4\">Host:</font> <font color=\"#0090B0\" size=\"4\">$server</font><br />\r\n";

      foreach my $report (@{$server_errors{$server}})
      {
        print OUT $report;
      }

      print OUT "<br />\r\n";
    }
    print OUT "</p>\r\n";

    print OUT "<p>If you wish to stop receiving these emails please email hh.emea\@xxxxxx.net<br /></p>\r\n";
  }

  print OUT "<p style=\"color:red;\">This is an automated email, please do not reply.</p>\r\n";

  print OUT "\r\n--$boundary\r\n";
  print OUT "Content-Type: application/x-gzip; name=\"${htmlfile}.gz\"\r\n";
  print OUT "Content-Transfer-Encoding: base64\r\n";
  print OUT "Content-Encoding: gzip\r\n";
  print OUT "Content-Description: \"${htmlfile}.gz\"\r\n";
  print OUT "Content-Disposition: attachment; filename=\"${htmlfile}.gz\"\r\n\r\n";

  #
  # Compress the ESX report and add as an attachment.
  #
  my $compressed = "";
  gzip \$html_buf => \$compressed, ( 'Name' => "$htmlfile", 'Comment' => "X&X ESX Daily Report" );
  print OUT b64encode ($compressed);

  #
  # Begin the second attachment for Martin Royds.
  #
  print OUT "\r\n--$boundary\r\n";
  print OUT "Content-Type: application/x-gzip; name=\"${mpfile}.gz\"\r\n";
  print OUT "Content-Transfer-Encoding: base64\r\n";
  print OUT "Content-Encoding: gzip\r\n";
  print OUT "Content-Description: ${mpfile}.gz\r\n";
  print OUT "Content-Disposition: attachment; filename=\"${mpfile}.gz\"\r\n\r\n";

  #
  # Compress the Multipath report and add as an attachment.
  #
  gzip \$mrep_buf => \$compressed, ( 'Name' => "$mpfile", 'Comment' => "X&X Multipath Report" );
  print OUT b64encode ($compressed);

  #
  # Close the multipart mail
  #
  print OUT "\r\n--$boundary--\r\n";

  close (OUT);
}

#######################################################################
##                      EXECUTION BEGINS HERE                        ##
#######################################################################

#
while ( $#ARGV + 1 > 0 )
{
  my $arg = shift;

  for ( $arg )
  {
     /\-d/  && do { $debug     = shift;next};
     /\-ep/ && do { push(@ep_email, shift);next};
     /\-et/ && do { push(@et_email, shift);next};
     /\-f/  && do { $fulllist  = 1;next};
     /\-h/  && do { show_help;exit;next};
     /\-l/  && do { list_servers; exit;next};
     /\-s/  && do { my $server = shift; get_server($server) or die "Server is not known\n"; push(@servers, $server);next};
     /\-v/  && do { $verbose   = 1;next};
     /.*/   && do { show_help;print "ERROR: Unrecognised option: $arg\n\n"; exit 1};
  }
}

#
# Need to change the SSH command when running as svadmin
#
if ( $uname eq "svadmin" )
{
  $ENV{SSH_AUTH_SOCK} = "/usr/local/svvs/home/svadmin/.ssh/agent";
  $sshcmd             = "/usr/local/svvs/bin/ssh";
}
debug_print 1, "Using SSH: ${sshcmd} (for $uname)\n";

#
# Get command line arguments
vprint "Fetching disk usage for the VMs\n";
#
# Add a comment
#
my $cmd = "${sshcmd} " . get_server('lnhiesx13d') . " -x 'ls -ld /vmfs/[es]\*;/usr/local/svvs/bin/sudo /usr/sbin/vdf' |";
debug_print 1, $cmd . "\n";

open INP, $cmd;
while (<INP>)
{
  # eg. /vmfs/sanr535 -> vmhba1:0:35:1
  if ( /\/vmfs\/(.*)\s+\-\>\s+(.*)/ )
  {
    my ($sl,$fs) = ($1,$2);

    $dluns{$fs}->{'symlink'} = "/vmfs/" . $sl;
    $dsymlink{$sl}           = $fs;
  }
  # Filesystem   Size     Used    Avail   Use% Mounted on
  # vmhba0:1:0:1 16779861 9968213 6811648 59%  /vmfs/*
  elsif ( /(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(\/vmfs\/\S+)/ )
  {
    my ($fs,$sz,$us,$av,$pc,$mo) = ($1,$2,$3,$4,$5,$6);

    debug_print 3, "Fetch Disk Usage 1: \$fs=$fs,\$sz=$sz,\$us=$us,\$av=$av,\$pc=$pc,\$mo=$mo\n";

    $dluns{$fs}->{'size'}    = $sz;
    $dluns{$fs}->{'used'}    = $us;
    $dluns{$fs}->{'avail'}   = $av;
    $dluns{$fs}->{'percent'} = $pc;
    $dluns{$fs}->{'mount'}   = $mo;

    $dluns{$fs}->{'vdisks'}  = 0;
  }
}

my $cmd = "${sshcmd} " . get_server('lnhiesx01') . " -x 'ls -ld /vmfs/[es]\*;/usr/local/svvs/bin/sudo /usr/sbin/vdf' |";
debug_print 1, $cmd . "\n";

open INP, $cmd;
while (<INP>)
{
  # eg. /vmfs/sanr535 -> vmhba1:0:35:1
  if ( /\/vmfs\/(.*)\s+\-\>\s+(.*)/ )
  {
    my ($sl,$fs) = ($1,$2);

    $pluns{$fs}->{'symlink'} = "/vmfs/" . $sl;
    $psymlink{$sl}           = $fs;
  }
  # Filesystem   Size     Used    Avail   Use% Mounted on
  # vmhba0:1:0:1 16779861 9968213 6811648 59%  /vmfs/*
  elsif ( /(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(\/vmfs\/\S+)/ )
  {
    my ($fs,$sz,$us,$av,$pc,$mo) = ($1,$2,$3,$4,$5,$6);

    debug_print 3, "Fetch Disk Usage 2: \$fs=$fs,\$sz=$sz,\$us=$us,\$av=$av,\$pc=$pc,\$mo=$mo\n";

    $pluns{$fs}->{'size'}    = $sz;
    $pluns{$fs}->{'used'}    = $us;
    $pluns{$fs}->{'avail'}   = $av;
    $pluns{$fs}->{'percent'} = $pc;
    $pluns{$fs}->{'mount'}   = $mo;

    $pluns{$fs}->{'vdisks'}  = 0;
  }
}

if ( $#servers >= 0 )
{
  foreach my $server ( @servers )
  {
    get_mpreport $server;
    parse_server $server;
    get_disk_usage $server;
  }
}
else
{
  foreach my $server (sort(keys(%dev_servers)))
  {
    parse_server $server;
    get_disk_usage $server;
    get_mpreport $server;
  }

  foreach my $server (sort(keys(%prod_servers)))
  {
    parse_server $server;
    get_disk_usage $server;
    get_mpreport $server;
  }
}

#
# Need to prepare the Multipathing report into a single buffer
# for later compression, if required.
#
$mrep_buf = $html_start . "<div class='container'>\n";

#
# Print the Multi Path reports
#
foreach my $server (sort(keys(%mpreport)))
{
  $mrep_buf .= sprintf $begin_mpreport, $server;

  foreach my $disk (sort(keys(%{$mpreport{$server}})))
  {
    $mrep_buf .= sprintf $mpreport_line, $disk,
                 defined %{$mpreport{$server}{$disk}}->{0}->{'active'} && defined %{$mpreport{$server}{$disk}}->{0}->{'prefer'} ? "ok" :
                   defined %{$mpreport{$server}{$disk}}->{0}->{'active'} ? "active" :
                   defined %{$mpreport{$server}{$disk}}->{0}->{'prefer'} ? "pref" : "idle",
                 %{$mpreport{$server}{$disk}}->{0}->{'state'},
                 %{$mpreport{$server}{$disk}}->{0}->{'name'},
                 defined %{$mpreport{$server}{$disk}}->{1}->{'active'} && defined %{$mpreport{$server}{$disk}}->{1}->{'prefer'} ? "ok" :
                   defined %{$mpreport{$server}{$disk}}->{1}->{'active'} ? "active" :
                   defined %{$mpreport{$server}{$disk}}->{1}->{'prefer'} ? "pref" : "idle",
                 %{$mpreport{$server}{$disk}}->{1}->{'state'},
                 %{$mpreport{$server}{$disk}}->{1}->{'name'},
                 defined %{$mpreport{$server}{$disk}}->{2}->{'active'} && defined %{$mpreport{$server}{$disk}}->{2}->{'prefer'} ? "ok" :
                   defined %{$mpreport{$server}{$disk}}->{2}->{'active'} ? "active" :
                   defined %{$mpreport{$server}{$disk}}->{2}->{'prefer'} ? "pref" : "idle",
                 %{$mpreport{$server}{$disk}}->{2}->{'state'},
                 %{$mpreport{$server}{$disk}}->{2}->{'name'},
                 defined %{$mpreport{$server}{$disk}}->{3}->{'active'} && defined %{$mpreport{$server}{$disk}}->{3}->{'prefer'} ? "ok" :
                   defined %{$mpreport{$server}{$disk}}->{3}->{'active'} ? "active" :
                   defined %{$mpreport{$server}{$disk}}->{3}->{'prefer'} ? "pref" : "idle",
                 %{$mpreport{$server}{$disk}}->{3}->{'state'},
                 %{$mpreport{$server}{$disk}}->{3}->{'name'};
  }

  $mrep_buf .= $close_mpreport;
}

#
# Close the HTML body.
#
$mrep_buf .= "\n</div>\n</BODY>\n</HTML>\n";

#
# Get the REDO file information
#
get_redo_files;

#
# Send to an email address or to STDOUT
#
if ( $#ep_email >= 0 || $#et_email >= 0 )
{
  #
  # Display the main content.
  #
  foreach my $server (sort(keys(%serverinfo)))
  {
    prepare_html_attachment $server;
  }

  #
  # Print the html headers, etc. and open the outer container.
  #
  $html_buf = $html_start . $html_cont . $html_buf . "<div class='container'>\n";

  #
  # If we are not doing a full list we need to show the bad
  # VMs at the end of the server status overview.
  #
  if ( !$fulllist )
  {
    foreach my $server (sort(keys(%serverinfo)))
    {
      if ( defined @{$serverinfo{$server}->{'oob'}} )
      {
          $html_buf .= sprintf $begin_server, $server;

          foreach my $line ( @{$serverinfo{$server}->{'oob'}} )
          {
            $html_buf .= $line;
          }

          $html_buf .= "<!-- Close server container -->\n</div>\n";
      }
    }
  }

  #
  # Display VM disk usage
  #
  show_disk_usage;

  #
  # Display the REDO files
  #
  show_redo_files;

  #
  # Close the HTML body.
  #
  $html_buf .= "</BODY>\n</HTML>\n";

  #
  # Generate a quick synopsis of warnings/errors
  #
  error_report();

  #
  # Send the emails
  #
  if ( $#ep_email >= 0 )
  {
    send_to_email (0, @ep_email);
  }

  if ( $#et_email >= 0 )
  {
    send_to_email (1, @et_email);
  }
}
else
{
  "Sending output to STDOUT:\n\n";

  foreach my $server (sort(keys(%serverinfo)))
  {
    print_2_console $server;
  }

  #
  # Display disk usage
  #
  print "-" x 109 . "\n";
  print sprintf "| %-28s | %-28s | %5s | %5s | %7s | %7s | %7s |\n",
      "Mount Point", "Symbolic Link", "Disks", "VMs", "Size", "Used", "Avail";
  print "-" x 109 . "\n";
  foreach my $fs (sort(keys(%dluns)))
  {
    if ( defined %{$dluns{$fs}->{'vms'}} )
    {
      my $length = keys(%{$dluns{$fs}->{'vms'}});

      print sprintf "| %-28s | %-28s |  %4d |  %4d | %7s | %7s | %7s |\n",
          $dluns{$fs}->{'mount'}, $dluns{$fs}->{'symlink'}, $dluns{$fs}->{'vdisks'}, $length,
          pretty_print($dluns{$fs}->{'size'}), pretty_print($dluns{$fs}->{'used'}),
          pretty_print($dluns{$fs}->{'avail'});
    }
  }
  print "-" x 109 . "\n\n";

  print "-" x 109 . "\n";
  print sprintf "| %-28s | %-28s | %5s | %5s | %7s | %7s | %7s |\n",
      "Mount Point", "Symbolic Link", "Disks", "VMs", "Size", "Used", "Avail";
  print "-" x 109 . "\n";
  foreach my $fs (sort(keys(%pluns)))
  {
    if ( defined %{$pluns{$fs}->{'vms'}} )
    {
      my $length = keys(%{$pluns{$fs}->{'vms'}});

      print sprintf "| %-28s | %-28s |  %4d |  %4d | %7s | %7s | %7s |\n",
          $pluns{$fs}->{'mount'}, $pluns{$fs}->{'symlink'}, $pluns{$fs}->{'vdisks'}, $length,
          pretty_print($pluns{$fs}->{'size'}), pretty_print($pluns{$fs}->{'used'}),
          pretty_print($pluns{$fs}->{'avail'});
    }
  }
  print "-" x 109 . "\n\n";

  #
  # Display the REDO file information
  #
  print "-" x 142 . "\n";
  print sprintf "| %-15s | %-8s | %-109s |\n", "Server", "Size", "Command to commit the REDO file";
  print "-" x 142 . "\n";

  foreach my $redo (sort(keys(%dredos)))
  {
    # scsi0:0 <a> <b> <c>
    #
    # <a> = Level is used when you have one or more REDO files.
    # <b> = Freeze controls if you are going to freeze the VM while the redo
    #       log is played against the virtual disk.  0 = no, 1 = yes.
    # <c> = Wait.  If set to 1, the VM returns to operation as soon as commit
    #       begins else 0 means wait for commit to complete before continuing.
    #
    print sprintf "| %-15s | %-8s | vmware-cmd \"%-97s |\n",
          $dredos{$redo}->{'server'}, pretty_print($dredos{$redo}->{'size'} / 1024),
          $dredos{$redo}->{'cfgfile'} . "\" commit scsi" . $dredos{$redo}->{'disk'} . " 0 0 1";
    print sprintf "| %-15s | %-8s | vmCommit.pl -c \"%-93s |\n", "", "", $dredos{$redo}->{'cfgfile'} . "\"";
    print sprintf "| %-15s | %-8s | vmkfstools -m %-95s |\n", "", "", $dredos{$redo}->{'devfile'} . $dredos{$redo}->{'ext'};
    print "-" x 142 . "\n";
  }

  #
  # Display the REDO file information
  #
  print "-" x 142 . "\n";
  print sprintf "| %-15s | %-8s | %-109s |\n", "Server", "Size", "Command to commit the REDO file";
  print "-" x 142 . "\n";

  foreach my $redo (sort(keys(%predos)))
  {
    # scsi0:0 <a> <b> <c>
    #
    # <a> = Level is used when you have one or more REDO files.
    # <b> = Freeze controls if you are going to freeze the VM while the redo
    #       log is played against the virtual disk.  0 = no, 1 = yes.
    # <c> = Wait.  If set to 1, the VM returns to operation as soon as commit
    #       begins else 0 means wait for commit to complete before continuing.
    #
    print sprintf "| %-15s | %-8s | vmware-cmd \"%-97s |\n",
          $predos{$redo}->{'server'}, pretty_print($predos{$redo}->{'size'} / 1024),
          $predos{$redo}->{'cfgfile'} . "\" commit scsi" . $predos{$redo}->{'disk'} . " 0 0 1";
    print sprintf "| %-15s | %-8s | vmCommit.pl -c \"%-93s |\n", "", "", $predos{$redo}->{'cfgfile'} . "\"";
    print sprintf "| %-15s | %-8s | vmkfstools -m %-95s |\n", "", "", $predos{$redo}->{'devfile'} . $predos{$redo}->{'ext'};
    print "-" x 142 . "\n";
  }
}

