boxinfo: Fix instalation issue on recent platforms
authorDevrim Gunduz <devrim@gunduz.org>
Mon, 7 Apr 2025 07:48:15 +0000 (10:48 +0300)
committerDevrim Gunduz <devrim@gunduz.org>
Mon, 7 Apr 2025 07:48:15 +0000 (10:48 +0300)
rpm/redhat/main/common/boxinfo/EL-10/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/EL-8/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/EL-9/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/F-40/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/F-41/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/F-42/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/SLES-15/boxinfo.pl [new symlink]
rpm/redhat/main/common/boxinfo/main/boxinfo.pl [new file with mode: 0644]
rpm/redhat/main/common/boxinfo/main/boxinfo.spec

diff --git a/rpm/redhat/main/common/boxinfo/EL-10/boxinfo.pl b/rpm/redhat/main/common/boxinfo/EL-10/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/EL-8/boxinfo.pl b/rpm/redhat/main/common/boxinfo/EL-8/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/EL-9/boxinfo.pl b/rpm/redhat/main/common/boxinfo/EL-9/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/F-40/boxinfo.pl b/rpm/redhat/main/common/boxinfo/F-40/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/F-41/boxinfo.pl b/rpm/redhat/main/common/boxinfo/F-41/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/F-42/boxinfo.pl b/rpm/redhat/main/common/boxinfo/F-42/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/SLES-15/boxinfo.pl b/rpm/redhat/main/common/boxinfo/SLES-15/boxinfo.pl
new file mode 120000 (symlink)
index 0000000..ddd5809
--- /dev/null
@@ -0,0 +1 @@
+../main/boxinfo.pl
\ No newline at end of file
diff --git a/rpm/redhat/main/common/boxinfo/main/boxinfo.pl b/rpm/redhat/main/common/boxinfo/main/boxinfo.pl
new file mode 100644 (file)
index 0000000..4fc7475
--- /dev/null
@@ -0,0 +1,4026 @@
+#!/usr/bin/env perl
+# -*-mode:cperl; indent-tabs-mode: nil-*-
+
+## Gather important information about a box
+## Output in HTML or MediaWiki friendly format
+##
+## Greg Sabino Mullane <greg@endpoint.com>
+## Copyright End Point Corporation 2008-2017
+## BSD licensed
+## See: http://bucardo.org/wiki/Boxinfo
+
+use strict;
+use 5.006001;
+use warnings;
+use Data::Dumper   qw{ Dumper     };
+use Getopt::Long   qw{ GetOptions };
+use File::Basename qw{ basename   };
+
+our $VERSION = '1.5.0';
+
+my $USAGE = "Usage: $0 <options>
+ Important options:
+ --verbose
+ --skippgport=#
+ --postgresonly
+Set ENV{PG_CONFIG} if not in the path (or adjust your path!)
+
+For complete help, please visit:
+http://bucardo.org/wiki/Boxinfo
+
+";
+
+my %opt;
+GetOptions(
+    \%opt,
+    'version',
+    'verbose',
+    'quiet',
+    'help',
+    'format=s',
+    'os=s',
+    'client=s',
+    'skippgport=s',
+    'postgresonly',
+    'nopostgres',
+    'postgresnohost',
+    'nohost',
+    'nomysql',
+    'nosendmail',
+    'useballoons',
+    'timeout=i',
+) or die $USAGE;
+
+$opt{help} and die $USAGE;
+
+if ($opt{version}) {
+    print "$0 version $VERSION\n";
+    exit 0;
+}
+
+my $OS           = $opt{os} || $^O;
+my $quiet        = $opt{quiet} || 0;
+my $verbose      = $opt{verbose} || 0;
+my $use_balloons = exists $opt{useballoons} ? $opt{useballoons} : 1;
+my $timeout      = exists $opt{timeout} ? $opt{timeout} : 10;
+my $format       = $opt{format} || 'wiki';
+
+$opt{use_su_postgres} = 0;
+
+## Used to identify the version of programs and Perl modules
+my $versionre = qr{\d+\.\d+(?:\S*)};
+
+## Inline CSS
+my $vtop = ' style="vertical-align: top"';
+
+## For ease of writing the HTML later on
+my $wrap = '<br />';
+
+## What to print if we can't figure out what something is
+my $UNKNOWN_VALUE = '?';
+my $UNKNOWN_VERSION = '?';
+
+## Dump the actual output of each command into a debug file
+my $debugfile = 'boxinfo.debug';
+open my $debugfh, '>', $debugfile or die qq{Could not write "$debugfile": $!\n};
+printf {$debugfh} "PROGRAM: $0\nStart: %s\n", scalar localtime();
+
+## When we leave for any reason, close the debug file and remove the temp file
+END {
+    defined $debugfh and (close $debugfh or die qq{Could not close "$debugfile": $!\n});
+    !$quiet and defined $debugfile and print "Raw data is in $debugfile\n";
+    unlink '/tmp/boxinfo.tmp';
+}
+
+## Output messages per language
+my %msg = (
+'en' => {
+    'time-day'           => q{day},
+    'time-days'          => q{days},
+    'time-hour'          => q{hour},
+    'time-hours'         => q{hours},
+    'time-minute'        => q{minute},
+    'time-minutes'       => q{minutes},
+    'time-month'         => q{month},
+    'time-months'        => q{months},
+    'time-second'        => q{second},
+    'time-seconds'       => q{seconds},
+    'time-week'          => q{week},
+    'time-weeks'         => q{weeks},
+    'time-year'          => q{year},
+    'time-years'         => q{years},
+},
+'fr' => {
+    'time-day'           => q{jour},
+    'time-days'          => q{jours},
+    'time-hour'          => q{heure},
+    'time-hours'         => q{heures},
+    'time-minute'        => q{minute},
+    'time-minutes'       => q{minutes},
+    'time-month'         => q{mois},
+    'time-months'        => q{mois},
+    'time-second'        => q{seconde},
+    'time-seconds'       => q{secondes},
+    'time-week'          => q{semaine},
+    'time-weeks'         => q{semaines},
+    'time-year'          => q{année},
+    'time-years'         => q{années},
+},
+'de' => {
+},
+'es' => {
+},
+);
+
+## Figure out which language to use for output
+our $lang = $ENV{LC_ALL} || $ENV{LC_MESSAGES} || $ENV{LANG} || 'en';
+$lang = substr($lang,0,2);
+
+## Figure out our hostname and the short version
+my $hostname = qx{hostname};
+chomp $hostname;
+if (length $hostname) {
+    print {$debugfh} "HOST: $hostname\n";
+}
+my $shorthost = $hostname;
+if ($shorthost =~ m{^(\w+\d\.\w+)\.\w+\.\w+$}) {
+    $shorthost = $1;
+}
+else {
+    $shorthost =~ s/^(.+?)\..+/$1/;
+}
+
+## Pick a client name for use in the page title
+my $clientname = 'ACME';
+if ($opt{client}) {
+    $clientname = $opt{client};
+}
+elsif ($hostname =~ m{([^\.]+)\.\w+$}) {
+    $clientname = ucfirst lc $1;
+}
+
+## We put the current time at the top of the report
+my $nowtime = qx{date};
+chomp $nowtime;
+
+my %data = (
+    program_version => $VERSION,
+    program_name    => basename ($0),
+    program_start   => $nowtime,
+    OS              => $OS,
+    hostname        => $hostname,
+    shorthost       => $shorthost,
+    clientname      => $clientname,
+);
+
+my %distlist = (
+    'redhat'    => ['redhat-release',    'Red Hat',   'release' ],
+    'fedora'    => ['fedora-release',    'Fedora',    'release' ],
+    'SuSE'      => ['SuSE-release',      'SuSE',      'release' ],
+    'gentoo'    => ['gentoo-release',    'Gentoo',    'release' ],
+    'debian'    => ['debian_version',    'Debian',    'version' ],
+    'slackware' => ['slackware-release', 'Slackware', 'release' ],
+    'mandrake'  => ['mandrake-release',  'Mandrake',  'release' ],
+    'yellowdog' => ['yellowdog-release', 'Yellowdod', 'release' ],
+);
+
+## Gather versions of all kinds of programs (should be run first)
+gather_versions();
+
+if ($opt{postgresonly}) {
+    ## Postgres information (generic)
+    gather_postgresinfo();
+    goto GATHERDONE;
+}
+
+## yum and apt-get : what's installed?
+gather_package_info();
+
+## Postgres information
+gather_postgresinfo();
+
+## MySQL information
+gather_mysqlinfo();
+
+## uname
+gather_uname();
+
+## lsb-release and redhat-release
+gather_dist() if $OS eq 'linux';
+
+## memory is very OS specific
+gather_memory();
+
+## Is it a VM?
+gather_vminfo();
+
+## RightScale information
+gather_rightscale();
+
+## Grab all cron information we can find
+gather_croninfo();
+
+## All information on disks and filesystems
+gather_mounts();
+
+## Detailed info on each disk
+#gather_disk_settings();
+
+## Size of disks
+gather_disksize();
+
+## User limits
+gather_ulimits();
+
+## Environment variables
+gather_envs();
+
+## Network interface (ethernet cards)
+gather_interfaces();
+
+## Network routing information
+gather_routes();
+
+## What is set to run on boot?
+gather_chkconfig();
+
+## CPU information
+gather_cpuinfo();
+
+## Gather Perl module versions and detailed Perl information
+gather_perlinfo();
+
+## Information on the uptime
+gather_uptime();
+
+## Puppet info
+gather_puppet();
+
+## Loaded modules
+gather_lsmod();
+
+## LifeKeeper information
+gather_lifekeeper();
+
+## Heartbeat (Linux HA information)
+gather_heartbeat();
+
+## Ruby gems (local)
+gather_gems();
+
+GATHERDONE:
+
+## remove any temporary constructs from the hash
+for (keys %data) {
+    delete $data{$_} if $_ =~ /^tmp_/;
+}
+
+if ('wiki' eq $format or 'html' eq $format) {
+    create_html_output();
+}
+else {
+    die "Unknown format: must be 'html' or 'wiki'\n";
+}
+
+exit;
+
+
+sub slurp_table_info {
+    my $arg = shift or die;
+
+    run_command($arg->{command},'tmp_slurp');
+    my $slinfo = $data{tmp_slurp};
+    if ($arg->{failregex} and $slinfo =~ /$arg->{failregex}/) {
+        warn "$arg->{failregexmsg}\n";
+        return;
+    }
+    return unless $slinfo =~ /\w/;
+    my ($n,$v);
+    my $currname = '';
+    for my $line (split /\n/ => $slinfo) {
+        next if $line !~ /^(\w+).*\| (.*)/o;
+        ($n,$v) = (lc $1,$2||'');
+        if ($n eq $arg->{pk}) {
+            $currname = $v;
+        }
+        $arg->{var}{$currname}{$n} = $v;
+    }
+
+    return;
+
+} ## end of slurp_table_info
+
+
+sub gather_uname {
+
+    if ($OS eq 'linux') {
+        run_command('uname --kernel-name' => 'Kernel name');
+        run_command('uname --nodename' => 'Node name');
+        run_command('uname --kernel-release' => 'Kernel release');
+        run_command('uname --kernel-version' => 'Kernel version');
+        run_command('uname --machine' => 'Hardware name');
+        run_command('uname --processor' => 'Processor');
+        run_command('uname --hardware-platform' => 'Hardware platform');
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('uname -s' => 'Kernel name');
+        run_command('uname -n' => 'Node name');
+        run_command('uname -r' => 'Kernel release');
+        run_command('uname -v' => 'Kernel version');
+        run_command('uname -p' => 'Processor');
+        run_command('uname -m' => 'Hardware platform');
+    }
+    else {
+        $quiet or warn qq{Cannot gather uname information on OS "$OS"\n};
+    }
+
+    return;
+
+} ## end of gather_uname
+
+
+sub gather_dist {
+
+    my $lsb1 = '/etc/lsb-release';
+    my $lsb2 = '/usr/bin/lsb_release';
+    if (-e $lsb1) {
+        open my $fh, '<', $lsb1 or die qq{Could not open "$lsb1": $!\n};
+        while (<$fh>) {
+            if (/^DISTRIB_(.+?)=(.+)/) {
+                my ($nam,$val) = ($1,$2);
+                $val =~ s/^"(.+)"$/$1/;
+                $data{lsb_release}{$nam} = $val;
+            }
+            elsif (/^LSB_VERSION="(\d[\.\d]*)"/) {
+                $data{lsb_version} = $1;
+            }
+            else {
+                $quiet or warn qq{Unknown line $. in $lsb1: $_\n};
+            }
+        }
+        close $fh or die qq{Could not close "$lsb1": $!\n};
+    }
+    ## Check for lsb_release executable for the codename
+    if (-e $lsb2) {
+        run_command("$lsb2 -a", 'tmp_lsb');
+        if ($data{tmp_lsb} =~ /Codename:\s+(.+)/) {
+            $data{dist_codename} = $1;
+        }
+    }
+
+    for my $dist (keys %distlist) {
+        my $file = "/etc/$distlist{$dist}->[0]";
+        if (-e $file) {
+            open my $fh, '<', $file or die qq{Could not open "$file": $!\n};
+            while (<$fh>) {
+                chomp;
+                $data{dist}{$dist} = $_;
+                last;
+            }
+            close $fh or die qq{Could not close "$file": $!\n};
+        }
+    }
+
+    return;
+
+} ## end of gather_dist
+
+
+sub gather_vminfo {
+
+    $data{VM} = 'No';
+
+    ## Check for a EC2 motd
+    my $file = '/etc/motd';
+    if (-e $file) {
+        if (open my $fh, '<', $file) {
+        my $slurp;
+        { local $/; $slurp = <$fh>; }
+        close $fh or warn qq{Could not close "$file": $!\n};
+        if ($slurp =~ /eip:\s+(\S+)\ninstance id:\s+(\S+)\ntype:\s+(\S+)/) {
+            ($data{EC2}{eip}, $data{EC2}{id}, $data{EC2}{type}) = ($1,$2,$3);
+        }
+        }
+    }
+
+    ## Find the EC2 ami version
+    $file = '/usr/bin/ec2-ami-tools-version';
+    if (-x $file) {
+        run_command($file, 'tmp_ec2');
+        if ($data{tmp_ec2} =~ /\d/) {
+            $data{EC2}{ami} = $data{tmp_ec2};
+        }
+
+        ## Grab the rest of the EC2 information on the fly
+        if (exists $data{version}{curl}) {
+            my %meta;
+            my $uri = 'http://169.254.169.254/latest/meta-data/';
+            parse_ec2_uri(\%meta, $uri);
+            sub parse_ec2_uri {
+                my ($tempname, $tempurl) = @_;
+                my $info = qx{curl -s $tempurl};
+                for my $word (split /\n/ => $info) {
+                    if ($word =~ m{/$}) {
+                        $tempname->{$word} = {};
+                        parse_ec2_uri($tempname->{$word}, "${tempurl}$word");
+                    }
+                    elsif ($word =~ /\w$/) {
+                        my $uri = "${tempurl}$word";
+                        $tempname->{$word} = qx{curl -s $uri};
+                    }
+                }
+                return;
+            }
+            $data{EC2}{meta} = \%meta;
+        }
+    }
+
+    if ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('ps -o stat', 'tmp_ps');
+        if ($data{tmp_ps} =~ /J$/m) {
+            $data{VM} = 'BSD jail';
+            return;
+        }
+    }
+
+    run_command('dmidecode', 'tmp_dmi');
+    if ($data{tmp_dmi} =~ /VMware/) {
+        $data{VM} = 'VMware';
+        return;
+    }
+
+    if ($data{tmp_dmi} =~ /Manufacturer: Microsoft/) {
+        $data{VM} = 'VirtualPC';
+        return;
+    }
+
+    if (-e '/proc/xen/capabilities') {
+        $data{VM} = sprintf 'Xen %s', -e '/proc/xen/grant' ? '3.x' : '2.x';
+        return;
+        ## Capture xm list?
+    }
+
+    return;
+
+} ## end of gather_vminfo
+
+
+sub gather_rightscale {
+
+
+    ## If /etc/rightscale.d exists, we can be pretty sure this is a RightScale box
+
+    my $dir = '/etc/rightscale.d';
+
+    return if ! -d $dir;
+
+    $data{RightScale} = {};
+
+    ## What type of cloud?
+    my $file = "$dir/cloud";
+    if (open my $fh, '<', $file) {
+        ## Simply grab the first line
+        $data{RightScale}{cloud} = <$fh>;
+        chomp $data{RightScale}{cloud};
+        close $fh or warn qq{Could not close "$file": $!\n};
+    }
+
+    ## What version?
+    $file = "$dir/rightscale-release";
+    if (open my $fh, '<', $file) {
+        ## Simply grab the first line
+        $data{RightScale}{version} = <$fh>;
+        chomp $data{RightScale}{version};
+        close $fh or warn qq{Could not close "$file": $!\n};
+    }
+
+    return;
+
+} ## end of gather_rightscale
+
+
+sub gather_memory {
+
+    if ($OS eq 'linux') {
+        run_command('cat /proc/meminfo', 'tmp_meminfo');
+        ## Extract ueful bits out and erase the main entry
+        ## Seems to be all in kB
+        my $info = $data{'tmp_meminfo'};
+        my @memstuff = (
+            ['Total',  'MemTotal'],
+            ['Free',   'MemFree'],
+            ['Cached', 'Cached'],
+            ['Active', 'Active'],
+            ['Inactive', 'Inactive|Inact_clean'],
+            ['Swap Total', 'SwapTotal'],
+            ['Swap Free', 'SwapFree'],
+            ['HPsize', 'Hugepagesize'],
+            ['HPtotal', 'HugePages_Total'],
+            ['HPfree', 'HugePages_Free'],
+            ['HPreserved', 'HugePages_Rsvd'],
+            );
+        for (@memstuff) {
+            my ($name,$olabel) = @$_;
+            my $found = 0;
+            for my $label (split /\s*\|\s*/ => $olabel) {
+                if ($info =~ /$label:\s+(\d+) kB/) {
+                    my $val = $1 * 1024;
+                    $data{memory}{$name} = $val;
+                    $data{memory}{pretty}{$name} = pretty_size($val);
+                    $found = 1;
+                    last;
+                }
+                elsif ($info =~ /$label:\s+(\d+)/) {
+                  $data{memory}{$name} = $1;
+                  $found = 1;
+                  last;
+                }
+            }
+            if (!$found and $name !~ /^HP/) {
+                $quiet or warn qq{Could not determine "$name" from meminfo output\n};
+            }
+        }
+
+        ## Now shared memory settings
+        for my $name (qw/shmmax shmmni shmall/) {
+            run_command("cat /proc/sys/kernel/$name", 'tmp_shm');
+            $data{memory}{$name} = delete $data{'tmp_shm'};
+            if ($name !~ /i$/) {
+                $data{memory}{pretty}{$name} = pretty_size($data{memory}{$name});
+            }
+        }
+        run_command('ipcs -m', 'tmp_active_shared_memory');
+        $data{memory}{active_shared} = ($data{tmp_active_shared_memory} =~ y/\n/\n/) - 3;
+        $data{memory}{active_shared} = 0 if $data{tmp_active_shared_memory} !~ /\d/;
+
+        run_command('ipcs -s', 'tmp_active_semaphores');
+        $data{memory}{active_semaphores} = ($data{tmp_active_semaphores} =~ y/\n/\n/) - 3;
+        $data{memory}{active_semaphores} = 0 if $data{tmp_active_semaphores} !~ /\d/;
+
+        run_command('ipcs -q', 'tmp_active_message_queues');
+        $data{memory}{active_messages} = ($data{tmp_active_message_queues} =~ y/\n/\n/) - 3;
+        $data{memory}{active_messages} = 0 if $data{tmp_active_message_queues} !~ /\d/;
+
+        run_command('ipcs -u', 'tmp_ipcs_summary');
+        ## Future: parse the above
+
+        ## Major VM tunables
+        for my $vm (qw/swappiness dirty_ratio dirty_background_ratio nr_hugepages/) {
+            run_command("cat /proc/sys/vm/$vm", 'tmp_swap');
+            if ($data{'tmp_swap'} =~ /\d/) {
+                $data{vm}{$vm} = $data{'tmp_swap'};
+            }
+        }
+
+        ## Huge pages
+        run_command('cat /sys/kernel/mm/transparent_hugepage/enabled', 'tmp_hugetemp');
+        if ($data{tmp_hugetemp} =~ /always/) {
+            $data{memory}{transparent_hugepages} = $data{tmp_hugetemp};
+        }
+
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        exists $data{'tmp_sysctl'} or run_command('sysctl -a', 'tmp_sysctl');
+        my $info = $data{'tmp_sysctl'};
+
+        my @memstuff = (
+            ['Total',   'hw.physmem'],
+            ['User',    'hw.usermem'],
+            ['Real',    'hw.realmem'],
+            ['VM kmem', 'vm.kmem_size'],
+            );
+        for (@memstuff) {
+            my ($name,$label) = @$_;
+            if ($info =~ /$label\s*[=:]\s*(\d+)/) {
+                my $val = $1;
+                $data{memory}{$name} = $val;
+                $data{memory}{pretty}{$name} = pretty_size($val);
+            }
+            elsif (!$quiet) {
+                if ($OS !~ /darwin/ or $label !~ /realmem|kmem/) {
+                    warn qq{Could not find "$label" in sysctl output\n};
+                }
+            }
+        }
+        run_command('ipcs -m', 'tmp_active_shared_memory');
+        $data{memory}{active_shared} = ($data{tmp_active_shared_memory} =~ y/\n/\n/) - 3;
+        $data{memory}{active_shared} = 0 if $data{tmp_active_shared_memory} !~ /\d/;
+
+        run_command('ipcs -s', 'tmp_active_semaphores');
+        $data{memory}{active_semaphores} = ($data{tmp_active_semaphores} =~ y/\n/\n/) - 3;
+        $data{memory}{active_semaphores} = 0 if $data{tmp_active_semaphores} !~ /\d/;
+
+        run_command('ipcs -q', 'tmp_active_message_queues');
+        $data{memory}{active_message} = ($data{tmp_active_message_queues} =~ y/\n/\n/) - 3;
+        $data{memory}{active_messages} = 0 if
+            (! defined $data{memory}{active_messages} or $data{tmp_active_message_queues} !~ /\d/);
+
+        run_command('ipcs -a', 'tmp_ipcs_all');
+        run_command('ipcs -T', 'tmp_ipcs_T');
+    }
+    else {
+        $quiet or warn qq{Do not know how to check OS "$OS" for memory\n};
+    }
+
+    return;
+
+} ## end of gather_memory
+
+
+sub gather_croninfo {
+
+    if ($OS eq 'linux') {
+        run_command('cat /etc/crontab', 'tmp_etc_crontab');
+        my $info = $data{tmp_etc_crontab};
+        for my $var (qw/d hourly daily weekly monthly/) {
+            my $crondir = "/etc/cron.$var";
+            if (opendir my $dirh, $crondir) {
+                for my $file (grep { /^\w/ } readdir $dirh) {
+                    run_command("cat $crondir/$file", 'tmp_cron');
+                    $info = $data{tmp_cron};
+                }
+            }
+        }
+        my $crondir = '/var/spool/cron';
+        if (opendir my $dirh, $crondir) {
+            for my $file (grep { /^\w/ } readdir $dirh) {
+                run_command("cat $crondir/$file", 'tmp_cron');
+                $info = $data{tmp_cron};
+            }
+        }
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('cat /etc/crontab', 'tmp_crontab');
+        my $info = $data{tmp_crontab};
+    }
+    else {
+        $quiet or warn qq{Do not know how to find cron infomation on OS "$OS"\n};
+    }
+
+    return;
+
+} ## end of gather_croninfo
+
+sub gather_mounts {
+
+    if ($OS eq 'linux') {
+        run_command('mount -l -f', 'tmp_mount');
+        for my $line (split /\n/ => $data{tmp_mount}) {
+            if ($line =~ m{^(.+?) on (.+?) type (.+?) \((.+)\)\s*(.*)}) {
+                my ($dev,$dir,$type,$options,$label) = ($1,$2,$3,$4,$5);
+                $data{fs}{$dev} = {dir => $dir, type => $type, options => $options, readahead => '', scheduler => ''};
+                if ($dev =~ m{^\/dev\/([a-z]+)\d*$}) {
+                    my $name = $1;
+                    if (! exists $data{block}{$name}) {
+                        run_command("cat /sys/block/$name/queue/read_ahead_kb", 'tmp_readahead');
+                        if ($data{tmp_readahead} =~ /(\d+)/) {
+                            $data{block}{$name}{readahead} = (($1 * 512) / 1024) . ' KB';
+                        }
+                        else {
+                            $data{block}{$name}{readahead} = '?';
+                        }
+                        run_command("cat /sys/block/$name/queue/scheduler", 'tmp_scheduler');
+                        my $sc = $data{tmp_scheduler} || '?';
+                        $data{block}{$name}{scheduler} = $sc =~ /^cat/ ? '?' : $sc;
+                        ## shows all available
+                    }
+                }
+                if ($label =~ s/\[(.+)\]/$1/) {
+                    $data{fs}{$dev}{label} = $label;
+                }
+            }
+            else {
+                $quiet or warn qq{Do not know how to parse df line: $line\n};
+            }
+        }
+        if (-f '/proc/mdstat') {
+            run_command('cat /proc/mdstat', 'tmp_mdstat');
+            if ($data{tmp_mdstat} =~ /Personalities : \S/) {
+                $data{mdstat} = $data{tmp_mdstat};
+            }
+        }
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('mount -d -v', 'tmp_mount');
+        for my $line (split /\n/ => $data{tmp_mount}) {
+            ## Example: /dev/foo on / (ufs, local)
+            if ($line =~ m{^(.+?) on (.+?)(?: \((.+?)\))*}) {
+                my ($dev,$dir,$type,$options,$label) = ($1,$2,$3||'','','');
+                my @options;
+                ($type, @options) = split /, /, $type;
+                $options = join ', ', @options;
+                $data{fs}{$dev} = {dir => $dir, type => $type, options => $options};
+                if ($label =~ s/\[(.+)\]/$1/) {
+                    $data{fs}{$dev}{label} = $label;
+                }
+            }
+            else {
+                $quiet or warn qq{Do not know how to parse df line: $line\n};
+            }
+        }
+    }
+    else {
+        $quiet or die qq{Do not know how to get mount information on OS "$OS"\n};
+    }
+
+    return;
+
+}  ## end of gather_mounts
+
+
+sub gather_disksize {
+
+    if ($OS eq 'linux' or $OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('df -h -P', 'tmp_disk_space');
+        run_command('df -i -P', 'tmp_disk_space_inodes');
+        my $info = $data{tmp_disk_space};
+        for my $line (split /\n/ => $info) {
+            next if $line =~ /^Filesystem/;
+            next unless $line =~ s{^(.+?)\s+(\d)}{$2};
+            my $name = $1;
+            my @info = split /\s+/ => $line;
+            if ($OS eq 'linux') {
+                $data{fs}{$name}{size} = $info[0];
+                $data{fs}{$name}{used} = $info[1];
+                $data{fs}{$name}{available} = $info[2];
+                $data{fs}{$name}{capacity} = $info[3];
+                $data{fs}{$name}{mounted} = $info[4];
+            }
+            else { ## BSD ... is not dead
+                $data{fs}{$name}{size} = $info[1] + $info[2];
+                $data{fs}{$name}{used} = $info[1];
+                $data{fs}{$name}{available} = $info[2];
+                $data{fs}{$name}{capacity} = $info[3];
+                $data{fs}{$name}{mounted} = $info[4];
+            }
+        }
+        $info = $data{tmp_disk_space_inodes};
+        for my $line (split /\n/ => $info) {
+            next if $line =~ /^Filesystem/;
+            next unless $line =~ s{^(.+?)\s+(\d)}{$2};
+            my $name = $1;
+            my @info = split /\s+/ => $line;
+            if ($OS eq 'linux') {
+                $data{fs}{$name}{inodes} = $info[0];
+                $data{fs}{$name}{inodes_used} = $info[1];
+                $data{fs}{$name}{inodes_free} = $info[2];
+                $data{fs}{$name}{inodes_usage} = $info[3];
+            }
+            else { ##BSD
+                $data{fs}{$name}{inodes} = $info[4] + $info[5];
+                $data{fs}{$name}{inodes_used} = $info[4];
+                $data{fs}{$name}{inodes_free} = $info[5];
+                $data{fs}{$name}{inodes_usage} = $info[6];
+            }
+        }
+    }
+    else {
+        $quiet or die qq{Do not know how to get disk size information on OS "$OS"\n};
+    }
+
+    return;
+
+}  ## end of gather_disksize
+
+
+sub gather_ulimits {
+
+    run_command(q{bash -c 'ulimit -a'}, 'tmp_ulimit');
+    for my $line (split /\n/ => $data{tmp_ulimit}) {
+        if ($line =~ /^(.+?)\s+\(.+\) (.+)/) {
+            $data{ulimit}{$1} = $2;
+        }
+        else {
+            $quiet or warn qq{Could not parse line of ulimit output: $line\n};
+        }
+    }
+
+    return;
+
+} ## end of gather_ulimits
+
+
+sub gather_envs {
+
+    for (keys %ENV) {
+        $data{ENV}{$_} = $ENV{$_};
+    }
+
+    return;
+
+} ## end of gather_envs
+
+
+sub gather_interfaces {
+
+    run_command('/sbin/ifconfig -v -a', 'tmp_ifconfig');
+    my $int = '?';
+    my $hwhex = qr{[a-fA-F0-9:\-]};
+    my $ip = qr{[0-9]+\.\d+\.\d+\.\d+};
+    for my $line (split /\n/ => $data{tmp_ifconfig}) {
+        if ($line =~ /^([\w:]+)/) {
+            $int = $1;
+        }
+        if ($line =~ /\s+HWaddr ($hwhex+)/) {
+            $data{interface}{$int}{hardware_address} = $1;
+        }
+        if ($line =~ /\s+ether ($hwhex+)/) {
+            $data{interface}{$int}{hardware_address} = $1;
+        }
+        if ($line =~ /\s+inet (?:addr:)?($ip)/) {
+            $data{interface}{$int}{address} = $1;
+        }
+        if ($line =~ /\s+Bcast:($ip)/i) {
+            $data{interface}{$int}{broadcast} = $1;
+        }
+        if ($line =~ /\s+Broadcast: ($ip)/i) {
+            $data{interface}{$int}{broadcast} = $1;
+        }
+        if ($line =~ /\s+Mask:($ip)/) {
+            $data{interface}{$int}{mask} = $1;
+        }
+        if ($line =~ /\s+netmask 0x(\S+)/) {
+            $data{interface}{$int}{mask} = $1;
+        }
+        if ($line =~ /\s+inet6 addr: (\S+)/) {
+            $data{interface}{$int}{inet6_address} = $1;
+        }
+        if ($line =~ /\bmtu[: ](\d+)/i) {
+            $data{interface}{$int}{mtu} = $1;
+            for my $opt (qw/UP BROADCAST RUNNING MULTICAST ALLMULTI PROMISC POINTOPOINT NOARP/) {
+                if ($line =~ /\b$opt\b/) {
+                    $data{interface}{$int}{$opt} = 1;
+                }
+            }
+        }
+        for my $opt ('collisions','txnqueuelen','RX bytes','TX bytes') {
+            if ($line =~ /\s+$opt:(\d+)/) {
+                $data{interface}{$int}{$opt} = $1;
+            }
+
+        }
+        if ($line =~ /\s+([RT])X packets:(\d+)/) {
+            my $x = lc $1;
+            $data{interface}{$int}{"${x}x_packets"} = $2;
+            for my $opt (qw/errors dropped overruns frame carrier/) {
+                if ($line =~ /\b$opt:(\d+)/) {
+                  $data{interface}{$int}{"${x}x_$opt"} = $1;
+              }
+            }
+        }
+        if ($line =~ /\boptions=/) {
+            while ($line =~ /([A-Z_]+)/g) {
+                $data{interface}{$int}{$1} = 1;
+            }
+        }
+    }
+
+    ## Check all hostnames for active interfaces, check the speed
+    for my $int (keys %{$data{interface}}) {
+        next if ! exists $data{interface}{$int}{UP};
+
+        run_command("ethtool $int", 'tmp_ethtool');
+        if ($data{tmp_ethtool} =~ /Speed: (\d+)(\S+)/) {
+            $data{interface}{$int}{current_speed} = "$1$2";
+            $data{interface}{$int}{nowspeed} = $1;
+        }
+        if ($data{tmp_ethtool} =~ /Duplex: (\S+)/) {
+            $data{interface}{$int}{duplex} = $1;
+        }
+
+        if ($data{tmp_ethtool} =~ /Supported link modes: (.+?)Sup/ms) {
+            my $sup = $1;
+            my $maxspeed = 0;
+            while ($sup =~ /(\d+)baseT/g) {
+                $maxspeed = $1 if $1 > $maxspeed;
+            }
+            $data{interface}{$int}{maxspeed} = $maxspeed;
+        }
+
+        next if exists $data{interface}{$int}{NOARP};
+        my $ip = $data{interface}{$int}{address};
+        ip2hostname($ip);
+    }
+
+    return;
+
+} ## end of gather_interfaces
+
+
+sub gather_routes {
+
+    if ($OS eq 'linux') {
+        run_command('route -n', 'tmp_route');
+        for my $line (split /\n/ => $data{tmp_route}) {
+            next unless $line =~ /^\d/o;
+            if ($line =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/o) {
+                my ($d,$gw,$m,$f,$mt,$ref,$use,$int) = ($1,$2,$3,$4,$5,$6,$7,$8);
+                push @{$data{route}}, {dest=>$d,gateway=>$gw,mask=>$m,flags=>$f,metric=>$mt,ref=>$ref,use=>$use,int=>$int};
+            }
+            else {
+                $quiet or warn "Could not parse line of route output: $line\n";
+            }
+        }
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        run_command('netstat -r', 'tmp_route');
+    }
+    else {
+        $quiet or warn qq{Do not know how to determine routing information on OS "$OS"\n};
+    }
+
+    return;
+
+} ## end of gather_routes
+
+
+sub gather_versions {
+
+    ## --version
+    for my $prog (qw/
+      apt-get aptitude autoconf awk bash cc check_postgres.pl chkconfig convert curl cvs dpkg dovecot
+      elinks emacs emerge find gcc gdb geos-config git gnome-panel gpg gzip iconv initdb interchange
+      knock konquerer links make mii-tool nano ntpd pdns_server perl pg_config pg_dump
+      postgres psql puppet python rrdtool rsync ruby s3cmd
+      screen sed service svn syslog syslog-ng tar tail_n_mail.pl tail_n_mail tcbmgr vi vim wget yum /) {
+        my $maxtime = $timeout;
+        if ('yum' eq $prog and 30 > $maxtime) {
+            $maxtime = 30;
+        }
+        run_command("$prog --version", 'tmp_version', $maxtime);
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## version
+    for my $prog (qw/ openssl /) {
+        run_command("$prog version", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## -version
+    for my $prog (qw/ sqlite/) {
+        run_command("$prog -version", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## -v
+    for my $prog (qw/ lighttpd php rsyslogd slonik /) {
+        run_command("$prog -v", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## -V
+    for my $prog (qw/ nginx pgbouncer rcs sar ssh /) {
+        run_command("$prog -V", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## -help
+    for my $prog (qw/ zip /) {
+        run_command("$prog -help", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## --help
+    for my $prog (qw/ bzip2 ethtool nrpe 7zr /) {
+        run_command("$prog --help", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## -h
+    for my $prog (qw/ memcached /) {
+        run_command("$prog -h", 'tmp_version');
+        $data{version}{$prog} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+    ## Special cases
+    run_command('postconf mail_version', 'tmp_version');
+    $data{version}{postfix} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+
+    if (! $opt{nosendmail}) {
+        run_command('echo \\\$Z | sendmail -bt -d0', 'tmp_version');
+        $data{version}{sendmail} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+    }
+
+    run_command('exim -bV', 'tmp_version');
+    $data{version}{exim} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+
+    run_command('mysql --version', 'tmp_version');
+    $data{version}{mysql} = ($data{tmp_version} =~ /($versionre) Distrib ([^,]+)/) ? "$2 ($1)" : $UNKNOWN_VERSION;
+
+    run_command('vsftpd -v 0>&1', 'tmp_version');
+    $data{version}{vsftpd} = ($data{tmp_version} =~ /($versionre)/) ? $1 : $UNKNOWN_VERSION;
+
+    run_command('thrift -version', 'tmp_version');
+    $data{version}{thrift} = ($data{tmp_version} =~ /(\d+)/) ? $1 : $UNKNOWN_VERSION;
+
+    ## Sometimes we get a trailing comma: remove
+    ## Also remove unmatched trailing parens
+    for (keys %{$data{version}}) {
+        $data{version}{$_} =~ s/,$//;
+        $data{version}{$_} =~ s/\)$// if $data{version}{$_} !~ /\(/;
+    }
+
+    return;
+
+} ## end of gather_versions
+
+
+sub gather_package_info {
+
+    ## Gather what's been installed via yum
+    if ($data{version}{yum} =~ /\d/) {
+        run_command('yum list installed', 'tmp_yumi');
+        for my $line (split /\n/ => $data{tmp_yumi}) {
+            next if $line !~ /(\S+)\s+(\S+)\s+(\S+)/o;
+            my ($name,$ver,$type) = ($1,$2,$3);
+            if ($type eq 'installed') {
+                $data{yum}{installed}{$name} = $ver;
+            }
+        }
+    }
+
+    ## Gather what's been installed via apt-get / dpkg
+    if ($data{version}{'apt-get'} =~ /\d/ and ! exists $data{tmp_yumi}) {
+        run_command('aptitude search ~i', 'tmp_apti');
+        for my $line (split /\n/ => $data{tmp_apti}) {
+            next if $line !~ /^i(..) (\S+)\s+\- (.+)/o;
+            my ($flag,$name,$desc) = ($1,$2,$3);
+            $data{aptitude}{installed}{$name} = $flag =~ /A/ ? 1 : 0;
+        }
+    }
+
+    return;
+
+} ## end of gather_package_info
+
+
+sub gather_chkconfig {
+
+    my $runlevel = qx{runlevel 2>/dev/null} =~ /(\d)$/ ? $1 : 3;
+
+    run_command(qq{chkconfig --list 2>/dev/null | grep '$runlevel:on'}, 'tmp_chkconfig');
+    for my $line (split /\n/ => $data{tmp_chkconfig}) {
+        if ($line =~ /^(\S+)/) {
+            $data{chkconfig}{$1} = 1;
+        }
+        else {
+            $quiet or warn qq{Could not parse line of chkconfig output: $line\n};
+        }
+    }
+
+    return;
+
+} ## end of gather_chkconfig
+
+
+sub gather_cpuinfo {
+
+    if ($OS eq 'linux') {
+        run_command('cat /proc/cpuinfo', 'tmp_cpuinfo');
+        my $info = $data{tmp_cpuinfo};
+        my $cpu = 0;
+        $data{numcpus} = 0;
+        while ($info =~ /^(.+?)\s+: (.+)/gm) {
+            my ($name,$value) = ($1,$2);
+            if ($name eq 'processor') {
+                $data{numcpus}++;
+                $cpu = $value;
+            }
+            if ($name eq 'cpu MHz') {
+                $data{cpuinfo}{$cpu}{speed} = (int $value) . ' MHz';
+            }
+            else {
+                $data{cpuinfo}{$cpu}{$name} = $value;
+            }
+        }
+    }
+    elsif ($OS =~ /bsd/ or $OS =~ /darwin/) {
+        exists $data{'tmp_sysctl'} or run_command('sysctl -a', 'tmp_sysctl');
+        my $info = $data{'tmp_sysctl'};
+        if ($info =~ /hw.model:\s+(.+)/ or $info =~ /machdep.cpu.brand_string:\s+(.+)/) {
+            $data{cpuinfo}{0}{'model name'} = $1;
+            if ($1 =~ / (.+GHz)/) {
+                $data{cpuinfo}{0}{speed} = $1;
+            }
+        }
+        if ($info =~ /hw.ncpu:\s+(.+)/) {
+            $data{numcpus} = $1;
+        }
+    }
+    else {
+        $quiet or warn qq{Do not know how to gather CPU information for OS "$OS"\n};
+    }
+
+    return;
+
+} ## end of gather_cpuinfo
+
+
+sub gather_perlinfo {
+
+    $data{perl}{INC} = \@INC;
+
+    run_command('perl -V', 'tmp_perl');
+    my $info = $data{tmp_perl};
+
+    $data{perl}{multiplicity} = $UNKNOWN_VALUE;
+    if ($info =~ /\busemultiplicity=(\w+)/) {
+        my $val = $1;
+        if ($val eq 'define') {
+            $data{perl}{multiplicity} = 'Yes';
+        }
+        elsif ($val eq 'undef') {
+            $data{perl}{multiplicity} = 'No';
+        }
+        else {
+            $quiet or warn qq{Unknown perl multiplicity value: $val\n};
+        }
+    }
+
+    $data{perl}{threads} = $UNKNOWN_VALUE;
+    if ($info =~ /\busethreads=(\w+)/) {
+        my $val = $1;
+        if ($val eq 'define') {
+            $data{perl}{threads} = 'Yes';
+        }
+        elsif ($val eq 'undef') {
+            $data{perl}{threads} = 'No';
+        }
+        else {
+            $quiet or warn qq{Unknown perl threads value: $val\n};
+        }
+    }
+
+    $data{perl}{ithreads} = $UNKNOWN_VALUE;
+    if ($info =~ /\buseithreads=(\w+)/) {
+        my $val = $1;
+        if ($val eq 'define') {
+            $data{perl}{ithreads} = 'Yes';
+        }
+        elsif ($val eq 'undef') {
+            $data{perl}{ithreads} = 'No';
+        }
+        else {
+            $quiet or warn qq{Unknown perl ithreads value: $val\n};
+        }
+    }
+
+    ## Modules of interest
+    for my $mod (qw/DBI DBD::Pg DBD::Oracle DBD::Sqlite DBD::mysql Moose Mail::Sendmail Sys::Syslog
+                    DBIx::Safe Time::HiRes Digest::MD5 YAML Net::SNMP/) {
+        delete $data{tmp_module};
+        my $COM = qq{perl -M$mod -e 'print \$${mod}::VERSION'};
+        run_command($COM, 'tmp_module');
+        if ($data{tmp_module} =~ /^($versionre)/) {
+          $data{perlmodver}{$mod} = $1;
+        }
+    }
+
+    return;
+
+} ## end of gather_perlinfo
+
+
+sub gather_uptime {
+
+    run_command('uptime', 'tmp_uptime');
+    my $info = $data{tmp_uptime};
+    if ($info =~ / up (\d+ \w+)/) {
+        $data{uptime} = $1;
+    }
+    if ($info =~ /(\d+) user/) {
+        $data{users_logged_in} = $1;
+    }
+    if ($info =~ /load averages?: ([\d\.]+)/) {
+        $data{load_average} = $1;
+    }
+
+    return;
+
+} ## end of gather_uptime
+
+
+sub gather_puppet {
+
+    return if ! exists $data{version}{puppet} or $data{version}{puppet} eq $UNKNOWN_VERSION;
+
+    run_command('puppet --genconfig', 'tmp_puppet');
+    my $info = $data{tmp_puppet};
+
+    if ($info !~ /^\s*classfile = (.+)/m) {
+        warn qq{puppet --genconfig did not reveal a classfile!\n};
+        return;
+    }
+    my $classfile = $1;
+    if (! -e $classfile) {
+        warn qq{puppet classfile "$classfile" does not exist!\n};
+        return;
+    }
+
+    run_command("cat $classfile", 'tmp_puppet_classes');
+    for my $line (split /\n/ => $data{tmp_puppet_classes}) {
+        chomp $line;
+        if ($line =~ /\w/) {
+            push @{$data{puppet}{class}} => $line;
+        }
+    }
+
+    return;
+
+} ## end of gather_puppet
+
+
+sub gather_lsmod {
+
+    run_command('lsmod', 'tmp_lsmod');
+    my $info = $data{tmp_lsmod};
+    while ($info =~ /^(\S+)\s+(\d+)\s+(.+)/gm) {
+        my ($mod,$size,$usedby) = ($1,$2,$3);
+        $data{lsmod}{$mod} = {size => $size, usedby => $usedby};
+    }
+
+    return;
+
+} ## end of gather_lsmod
+
+
+sub gather_lifekeeper {
+
+    run_command('lcdstatus -qu', 'tmp_lk');
+    my $info = $data{tmp_lk};
+    while ($info =~ /^(\S+)  (\S+).+?(\S+)\s+(\d+)\s+(\S+)$/gm) {
+        $data{lifekeeper}{service}{$2} =
+            {
+             local   => $1,
+             state   => $3,
+             prio    => $4,
+             primary => $5,
+             };
+    }
+    while ($info =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+)$/gm) {
+        $data{lifekeeper}{members}{"$1$3"} =
+            {
+             machine => $1,
+             network => $2,
+             address => $3,
+             state   => 4,
+             prio    => $5,
+             };
+    }
+
+    return;
+
+} ## end of gather_lifekeeper
+
+
+sub gather_heartbeat {
+
+    run_command('cl_status hbstatus', 'tmp_heartbeat');
+    my $info = $data{tmp_heartbeat};
+    if ($info =~ /Heartbeat is/) {
+        $data{heartbeat}{active} = $info;
+    }
+
+    run_command('cl_status listnodes', 'tmp_heartbeat');
+    $info = $data{tmp_heartbeat};
+    if ($info =~ /\w/ and $info !~ /ERROR:/) {
+        while ($info =~ /(\S+)/g) {
+            push @{$data{heartbeat}{node}}, $1;
+        }
+    }
+
+    for my $file (qw{ /etc/ha.cf /etc/ha.d/ha.cf }) {
+        if (-e $file) {
+            run_command("cat $file", 'tmp_heartbeat_config');
+            if (exists $data{tmp_heartbeat_config}) {
+                $data{heartbeat}{config} = $data{tmp_heartbeat_config};
+                last;
+            }
+        }
+    }
+
+    my $dir = '/etc/ha.d/resource.d';
+    if (-d $dir) {
+    my $dh;
+    if (opendir my $dh, $dir) {
+      my @rlist = grep { -f "$dir/$_" } readdir $dh;
+      closedir $dh;
+          $data{heartbeat}{resources} = \@rlist;
+        }
+    }
+
+    my $file = '/etc/ha.d/haresources';
+    if (-e $file) {
+      run_command("cat $file", 'tmp_heartbeat');
+      my $info = $data{tmp_heartbeat};
+      if ($info =~ /\w/) {
+    chomp $info;
+    $data{heartbeat}{haresources} = $info;
+      }
+    }
+
+    return;
+
+} ## end of gather_heartbeat
+
+
+sub gather_gems {
+
+    run_command('gem list --local', 'tmp_gems');
+    my $info = $data{tmp_gems};
+
+    return if $info !~ /\(\d+\.\d+/;
+
+    while ($info =~ m{^(\w+) \((\d+\.\d+\.?\d*)\)}gsm) {
+        $data{gems}{$1} = $2;
+    }
+
+    return;
+
+} ## end of gather_gems
+
+
+sub gather_postgresinfo {
+
+    return if $opt{nopostgres};
+
+    $ENV{PGUSER} ||= 'postgres';
+
+    my $pg_config = $ENV{PG_CONFIG} || 'pg_config';
+
+    run_command($pg_config, 'tmp_pgconfig');
+    my $info = $data{tmp_pgconfig};
+    if ($info !~ /BINDIR/) {
+        $verbose and warn "Call to $pg_config failed: custom path?\n";
+    }
+    else {
+        %{$data{postgres}{pgconfig}} = map { m/(.+?) = (.*)/; $1,$2; } split /\n/ => $info;
+    }
+
+    ## Only Linux, but we'll try all:
+    run_command('netstat -anp', 'tmp_netstat');
+    $info = $data{tmp_netstat};
+    $info =~ s{\d+/postgres: pos \S+PGSQL}{SKIP_OLD_POSTGRES}g;
+    $info =~ s{\d+/pgbouncer\s+\S+PGSQL\.\d}{SKIP_PGBOUNCER}g;
+
+    my ($oldpguser, $oldpgdb) = ('','');
+
+#unix  2      [ ACC ]     STREAM     LISTENING     568019   17851/postgres      socket/.s.PGSQL.5432
+
+  PG: while ($info =~ /^(.+?)((\S+)\.s\.PGSQL\.(\d+))$/mg) {
+        my ($extra,$cluster,$dir,$port) = ($1,$2,$3,$4);
+        next if exists $data{postgres}{active_port}{$cluster};
+        $data{postgres}{active_port}{$cluster} = { socketdir => $dir, port => $port, extra => $extra };
+        my $c = $data{postgres}{active_port}{$cluster};
+        my $pid = ($extra =~ /(\d+)\D+$/) ? $1 : '';
+
+        ## Let's grab the user as well - they may not have created a 'postgres' user within the database
+        run_command(qq{strings /proc/$pid/environ}, 'tmp_environ');
+        if ($data{tmp_environ} =~ /USER=(\w+)/) {
+            $c->{user} = $1;
+        }
+
+        ## Get the full path for this process
+        run_command(qq{pwdx $pid}, 'tmp_pwdx');
+        if ($data{tmp_pwdx} =~ m{\(deleted\)}) {
+            #warn "DELETED: skipping\n";
+            $c->{deleted} = 1;
+            next PG;
+        }
+        if ($data{tmp_pwdx} =~ /^\d+: (\S+)/) {
+            $c->{homedir} = $1;
+        }
+
+        ## If dir is relative, use the full path instead
+        if ($dir !~ m{^/} and $c->{homedir}) {
+            $dir = "$c->{homedir}/$dir";
+            $c->{socketdir} = $dir;
+        }
+
+        ## In case we swapped out the connection information
+        if ($oldpguser) {
+            $ENV{PGUSER} = $oldpguser;
+            $oldpguser = '';
+        }
+        if ($oldpgdb) {
+            $ENV{PGDATABASE} = $oldpgdb;
+            $oldpgdb = '';
+        }
+
+        ## Let's connect and get some information
+        my $usedir = $opt{postgresnohost} ? '' : "-h $dir";
+        my $PSQL = qq{psql -X -Ax -qt $usedir -p $port};
+
+        run_command(qq{psql -X -x -t $usedir -p $port -c "\\l+"}, 'tmp_psql');
+        my $pinfo = $data{tmp_psql};
+        ## Need to figure out the problem, but we cannot set lc_messages first!
+        my $problem = '';
+
+        if ($pinfo =~ /FATAL:  Ident authentication failed for user "postgres"/ and 0 == $>) {
+            $problem = 'ident';
+        }
+        elsif ($pinfo =~ /FATAL:  role.+does not exist/ and exists $c->{user}) {
+            $problem = 'role';
+        }
+        elsif ($pinfo =~ /ERROR.+shobj_description/) {
+            $problem = 'mismatch';
+        }
+        elsif ($pinfo =~ /FATAL.+postgres/) {
+            $problem = 'ident role mismatch';
+        }
+
+      RERUN: {
+            last if ! $problem;
+
+            $opt{use_su_postgres} = 0;
+
+            if ($problem =~ s/ident//) {
+                warn "Direct psql call failed, trying su -l postgres\n";
+                $opt{use_su_postgres} = 1;
+                run_command(qq{psql -X -x -t $usedir -p $port -c "\\l+"}, 'tmp_psql');
+                $pinfo = $data{tmp_psql};
+            }
+            elsif ($problem =~ s/role//) {
+                warn qq{Failed to connect to Postgres, retrying as user "$c->{user}"\n};
+                $ENV{PGUSER} = $c->{user};
+                $ENV{PGDATABASE} = 'postgres';
+                $opt{use_pg_user} = $c->{user};
+                run_command(qq{psql -X -x -t $usedir -p $port -c "\\l+"}, 'tmp_psql');
+                $pinfo = $data{tmp_psql};
+            }
+            elsif ($problem =~ s/mismatch//) {
+                ## Mismatched psql version, but non-plussed should work
+                run_command(qq{psql -X -x -t $usedir -p $port -c "\\l"}, 'tmp_psql');
+                $pinfo = $data{tmp_psql};
+            }
+            else {
+                warn "Unhandled problem: $problem\n";
+                $problem = '';
+            }
+
+            ## If we still have ways left to try and we failed in certain ways, try again
+            if ($problem =~ /\w/) {
+                if ($pinfo =~ /FATAL/ or $pinfo eq '?') {
+                    redo RERUN;
+                }
+            }
+        }
+
+        my $startupstring = 'database system is starting up';
+
+        if ($pinfo =~ /$startupstring/) {
+            warn "DB may be starting up, sleeping for a retry..\n";
+            sleep 2;
+            run_command(qq{psql -X x -t $usedir -p $port -c "\\l+"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            if ($pinfo =~ /$startupstring/) {
+                $c->{startingup} = 1;
+                ## Can we check for a recovery.conf file?
+
+                ## Make a good guess at the current data directory
+                my $datadir = '';
+
+                ## Figure out the PID
+                if ($extra =~ m{(\d+)/postgres}) {
+                    my $pid = $1;
+                    run_command("ps -Afw | grep $pid", 'tmp_pid');
+                    if ($data{tmp_pid} =~ /postgres -D (\S+)/) {
+                    $datadir = $1;
+                    }
+                }
+                if (! $datadir) {
+                    run_command(q{grep postgres /etc/passwd | cut -d":" -f 6}, 'tmp_datadir');
+                    if ($data{tmp_datadir} =~ m{^/}) {
+                    $datadir = "$data{tmp_datadir}/data";
+                    }
+                }
+
+                if ($datadir and -d $datadir) {
+                    $c->{datadir} = $datadir;
+                    my $recfile = "$datadir/recovery.conf";
+                    if (-e $recfile) {
+                        run_command("cat $recfile", 'tmp_recfile');
+                        $c->{recfile} = $data{tmp_recfile};
+                    }
+
+                    my $verfile = "$datadir/PG_VERSION";
+                    if (-e $verfile) {
+                        run_command("cat $verfile", 'tmp_verfile');
+                        $c->{version}{full} = $c->{version}{majmin} = $data{tmp_verfile};
+                    }
+
+                    for my $linkdir (qw/pg_xlog pg_log pg_clog/) {
+                        my $xfile = "$datadir/$linkdir";
+                        my $xlog = readlink($xfile);
+                        if (defined $xlog and $xlog ne $xfile) {
+                            $c->{symlink}{$linkdir} = $xlog;
+                        }
+                    }
+
+                    my $conffile = "$datadir/postgresql.conf";
+                    if (-e $conffile) {
+                        run_command("cat $conffile", 'tmp_conffile');
+                        my ($source, $unit) = ('File', '???');
+                    for my $line (split /\n/ => $data{tmp_conffile}) {
+                        $line =~ s/\s+$//;
+                        next if ! length $line;
+                        next if $line =~ /^\s*#/;
+                        if ($line =~ /^\s*(\w+)\s*=\s*(\w+)/) {
+                            $c->{setting}{$1} = { value => $2, source => $source, unit => $unit };
+                        }
+                        elsif ($line =~ /^\s*(\w+)\s*=\s*'(.*?)'/) {
+                            $c->{setting}{$1} = { value => $2, source => $source, unit => $unit };
+                        }
+                        else {
+                            warn ">>Could not parse postgresql.conf line: $line\n";
+                        }
+                    }
+                    if (!exists $c->{setting}{data_directory}) {
+                        $c->{setting}{data_directory} = { value => $datadir, source => 'Derived', unit => '' };
+                    }
+                    run_command("du -hs $datadir", 'tmp_ddsize');
+                    if ($data{tmp_ddsize} =~ /^(\d+\S+)/) {
+                        $c->{datadirsize}= $1;
+                    }
+
+                    }
+                } ## end found the data
+
+                ## Cause it to get skipped below:
+                $pinfo = 'FATAL: SKIP';
+            }
+        }
+
+        if (skip_pg_database($pinfo,$port,$dir)) {
+            $opt{use_su_postgres} = 0;
+            next PG;
+        }
+
+        ## If no match, see if we can get the complete socket
+        if ($pinfo !~ /\|/ and $dir !~ m{^/}) {
+            run_command("locate ${dir}.s.PGSQL.$port", 'tmp_psql');
+            if ($data{tmp_psql} =~ /^(.*${dir})\.s\.PGSQL\.$port\b/) {
+                my $newsock = $1;
+                my $sdir = "$newsock.s.PGSQL.$port";
+                if (-S $sdir) {
+                    $dir = $c->{largesocketdir} = $newsock;
+                    run_command(qq{psql -X -x -t -A $usedir -p $port -c "\\l+"}, 'tmp_psql');
+                    $pinfo = $data{tmp_psql};
+                    if ($pinfo !~ /\|/) {
+                        ## Try as the user that owns the database
+                        $oldpguser = $ENV{PGUSER} || 'postgres';
+                        $oldpgdb = $ENV{PGDATABASE} || 'postgres';
+                        run_command(qq{stat -c '\%U' $sdir}, 'tmp_stat');
+                        if ($data{tmp_stat} =~ /(\w+)/) {
+                            $ENV{PGUSER} = $1;
+                            $ENV{PGDATABASE} = 'postgres';
+                        }
+                        run_command(qq{psql -X -x -t -A $usedir -p $port -c "\\l+"}, 'tmp_psql');
+                        $pinfo = $data{tmp_psql};
+                    }
+                }
+            }
+        }
+        if ($pinfo !~ /\|/) {
+            warn "Could not find database at host $dir and port $port\n";
+            $opt{use_su_postgres} = 0;
+            next PG;
+        }
+
+        my ($n,$v);
+        my $currname = '';
+
+        for my $db (split /\n/ => $pinfo) {
+            if ($db =~ /^(\w+).*\| (.*)/) {
+                ($n,$v) = (lc $1,$2||'');
+                if ($n eq 'name') {
+                    $currname = $v;
+                }
+                $c->{db}{$currname}{$n} = $v;
+            }
+            elsif ($db =~ /^\-\-/) {
+                ## EOR
+            }
+            elsif ($db =~ /^\s+[:\|] (.+)/) {
+                $c->{db}{$currname}{$n} .= "\n$1";
+            }
+            else {
+                die "Unknown line: $db";
+            }
+        }
+
+        my $SQL = 'SELECT datname,datistemplate,datallowconn,datconnlimit,age(datfrozenxid),datacl,pg_database_size(oid) FROM pg_database';
+        run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL"}, 'tmp_psql');
+        $pinfo = $data{tmp_psql};
+        if ($pinfo =~ /ERROR/ or $pinfo =~ /FATAL/) {
+            warn "Could not connect to Postgres: $pinfo\n";
+            $opt{use_su_postgres} = 0;
+            next PG;
+        }
+
+        for my $db (split /\n/ => $pinfo) {
+            my ($name,$template,$canconn,$limit,$age,$acl,$size) = split /\|/ => $db;
+            my $info = $c->{db}{$name};
+            $info->{template} = $template eq 't' ? 'Yes' : 'No';
+            $info->{canconn} = $canconn eq 't' ? 'Yes' : 'No';
+            $info->{limit} = $limit;
+            $info->{acl} = $acl;
+            $info->{age} = $age;
+            $info->{size} = pretty_size($size,1);
+            ($info->{quoted_db_name} = $name) =~ s/"/\\"/g;
+        }
+
+        $SQL = 'SELECT version()';
+        run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL"}, 'tmp_psql');
+        $pinfo = $data{tmp_psql};
+        die $pinfo if $pinfo !~ /PostgreSQL (\S+)/;
+        my $ver = $1;
+        $c->{version}{full} = $ver;
+
+        if ($ver =~ /^(\d+)beta/) { ## e.g. 10beta1
+           $c->{version}{major} = $c->{version}{majmin} = $1;
+           $c->{version}{minor} = 0;
+        }
+        elsif ($ver =~ /^(\d+)\.(\d+)beta/) { ## e.g. 9.4beta1
+           $c->{version}{major} = $1;
+           $c->{version}{minor} = $2;
+           $c->{version}{majmin} = "$1$2";
+        }
+        elsif ($ver =~ /^(\d+)\.(\d+)\.(\d+)/) { ## e.g. 9.4.5
+            $c->{version}{major} = $1;
+            $c->{version}{minor} = $2;
+            $c->{version}{revision} = $3;
+            $ver = $c->{version}{majmin} = "$1.$2";
+        }
+        elsif ($ver =~ /^(\d+)\.(\d+)/) { ## e.g. 10.1
+            $c->{version}{major} = $c->{version}{majmin} = $1;
+            $c->{version}{minor} = 0;
+            $c->{version}{revision} = $2;
+        }
+        else {
+            die "Could not determine Postgres version from: $ver\n";
+        }
+        $c->{version}{revision} ||= 0;
+
+
+        ## Tablespace info
+        if ($c->{version}{majmin} >= 9.2) {
+            $SQL = 'SELECT spcname, spcowner, pg_tablespace_location(oid) as spclocation, spcacl FROM pg_tablespace';
+               } else {
+            $SQL = 'SELECT spcname, spcowner, spclocation, spcacl FROM pg_tablespace';
+        }
+        run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL"}, 'tmp_psql');
+        $pinfo = $data{tmp_psql};
+        for my $db (split /\n/ => $pinfo) {
+            my ($name,$owner,$location,$acl) = split /\|/ => $db;
+            next if ($name eq 'pg_default' or $name eq 'pg_global') and $location !~ /\w/;
+            my $info = $c->{dbtablespace}{$name} = [$location,$owner,$acl];
+            $data{gottablespaces} = 1;
+        }
+
+        $SQL = 'SELECT name,source,unit,setting FROM pg_settings';
+        if ($c->{version}{majmin} < 8.2) {
+            $SQL =~ s/unit/'?' AS unit/;
+        }
+        run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL"}, 'tmp_psql');
+        $pinfo = $data{tmp_psql};
+        die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+        for my $line (split /\n/ => $pinfo) {
+            my ($name,$source,$unit,$setting) = split /\|/ => $line => 4;
+            $c->{setting}{$name} = { value => $setting, source => $source, unit => $unit };
+        }
+
+        my $datadir = $c->{setting}{data_directory}{value};
+        for my $linkdir (qw/pg_xlog pg_log pg_clog/) {
+            my $xfile = "$datadir/$linkdir";
+            my $xlog = readlink($xfile);
+            if (defined $xlog and $xlog ne $xfile) {
+                $c->{symlink}{$linkdir} = $xlog;
+            }
+        }
+
+        ## Things individual to each database
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            my $qdb = $info->{quoted_db_name};
+            next if $info->{canconn} eq 'No';
+            $SQL = 'SELECT relkind, count(*) FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace) '.
+                q{WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema' GROUP BY 1};
+            run_command(qq{psql -X -t -A $usedir -p $port --dbname "$qdb" -c "$SQL"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+            for my $line (split /\n/ => $pinfo) {
+                my ($relkind,$count) = split /\|/ => $line;
+                $c->{db}{$db}{relcount}{$relkind} = $count;
+            }
+
+            ## Slony stuff
+            $SQL = q{SELECT nspname FROM pg_attribute a }
+                . q{JOIN pg_class c ON (c.oid = a.attrelid AND c.relname = 'sl_log_1') }
+                . q{JOIN pg_namespace n ON (n.oid = c.relnamespace) }
+                . q{WHERE attname = 'log_xid'};
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_slony');
+            $pinfo = $data{tmp_slony};
+            if ($pinfo =~ /\w/) {
+                my $slonyschema = $info->{slony}{schema} = $pinfo;
+                $data{gotslony}++;
+                $c->{gotslony}++;
+
+                $SQL = "SELECT pa_server || '-' || pa_client, pa_conninfo FROM $slonyschema.sl_path ORDER BY pa_server, pa_client";
+                run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_slony');
+                $pinfo = $data{tmp_slony};
+                for my $line (split /\n/ => $pinfo) {
+                    push @{$info->{slony}{paths}}, $line;
+                }
+
+                $SQL = "SELECT tab_nspname || '.' || tab_relname FROM $slonyschema.sl_table ORDER BY tab_id";
+                run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_slony');
+                $pinfo = $data{tmp_slony};
+                for my $line (split /\n/ => $pinfo) {
+                    push @{$info->{slony}{tables}}, $line;
+                }
+
+                $SQL = "SELECT seq_nspname || '.' || seq_relname FROM $slonyschema.sl_sequence ORDER BY seq_id";
+                run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_slony');
+                $pinfo = $data{tmp_slony};
+                for my $line (split /\n/ => $pinfo) {
+                    push @{$info->{slony}{sequences}}, $line;
+                }
+            } ## end Slony
+
+            ## Bucardo stuff
+          BUCARDO: {
+            ## Is this a master Bucardo database?
+            if ($qdb eq 'bucardo') {
+
+                $data{gotbucardo}++;
+                $c->{gotbucardo}++;
+
+                ## Slurp in information from various bucardo tables
+                ## Complain if the table does not exist
+                my %bucardo_table = (
+                    db             => 'name',
+                    sync           => 'name',
+                    goat           => 'id',
+                    herd           => 'name',
+                    bucardo_config => 'setting',
+                );
+                for my $tablename (sort keys %bucardo_table) {
+                    $info->{bucardo}{$tablename} ||= {};
+                    $SQL = qq{SELECT * FROM bucardo.$tablename};
+                    slurp_table_info({
+                        command      => qq{psql -X -x -t $usedir -p $port -c "$SQL" --dbname "$qdb"},
+                        var          => $info->{bucardo}{$tablename},
+                        pk           => $bucardo_table{$tablename},
+                        failregex    => "bucardo.$tablename",
+                        failregexmsg => "Odd - database named bucardo has no bucardo.$tablename table!",
+                     });
+                }
+
+            } ## end if db named bucardo
+            } ## end BUCARDO
+
+            ## pg_autovacuum settings
+            $SQL = q{SELECT * FROM pg_autovacuum};
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_slony');
+            $pinfo = $data{tmp_autovac};
+
+        } ## end each db
+
+        $SQL = q{SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname !~ '^pg_' AND nspname <> 'information_schema'};
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if $info->{canconn} eq 'No';
+            my $qdb = $info->{quoted_db_name};
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+            for my $line (split /\n/ => $pinfo) {
+                next if $line !~ /\w/;
+                $c->{db}{$db}{relcount}{schema}++;
+                $c->{db}{$db}{schema}{$line} = 1;
+            }
+        }
+
+        $SQL = 'SELECT count(*) FROM pg_proc p JOIN pg_namespace n ON (n.oid = p.pronamespace) '.
+            q{WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'};
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if $info->{canconn} eq 'No';
+            my $qdb = $info->{quoted_db_name};
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+            for my $line (split /\n/ => $pinfo) {
+                next unless $line =~ /(\d+)/;
+                $c->{db}{$db}{relcount}{f} = $1;
+            }
+        }
+
+        ## Languages
+        $SQL = 'SELECT lanname FROM pg_language WHERE lanispl IS TRUE';
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if $info->{canconn} eq 'No';
+            my $qdb = $info->{quoted_db_name};
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+            for my $line (split /\n/ => $pinfo) {
+                next unless $line =~ /(\w+)/;
+                $c->{db}{$db}{language}{$1}++;
+            }
+        }
+
+        ## Disabled triggers
+        my $SQL82 = q{SELECT tgrelid::regclass, tgname, tgenabled FROM pg_trigger WHERE tgenabled IS NOT TRUE ORDER BY tgname};
+        my $SQL83 = q{SELECT tgrelid::regclass, tgname, tgenabled FROM pg_trigger WHERE tgenabled = 'D' ORDER BY tgname};
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if $info->{canconn} eq 'No';
+            my $qdb = $info->{quoted_db_name};
+            $SQL = $c->{version}{majmin} >= 8.3 ? $SQL83 : $SQL82;
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            die $pinfo if $pinfo =~ /ERROR/ or $pinfo =~ /FATAL/;
+            for my $line (split /\n/ => $pinfo) {
+                if ($line !~ /(.+?)\|(.+?)\|(.+)/) {
+                    warn "Invalid line for trigger checks: $line\n";
+                    next;
+                }
+                push @{$c->{db}{$db}{disabled_triggers}}, [$1,$2,$3];
+                $data{postgres}{problems}++;
+                $c->{problems}++;
+                $c->{db}{$db}{problems}++;
+            }
+        }
+
+        ## Installed modules
+        for my $db (sort keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if $info->{canconn} eq 'No';
+            my $qdb = $info->{quoted_db_name};
+            ## PostGIS
+            $SQL = "SELECT 1 FROM pg_proc WHERE proname = 'postgis_full_version'";
+            run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+            $pinfo = $data{tmp_psql};
+            if ($pinfo =~ /1/) {
+                for my $name (qw/postgis_full_version postgis_lib_build_date postgis_scripts_build_date/) {
+                    $SQL = "SET lc_messages='C'; SELECT $name()";
+                    run_command(qq{psql -X -t -A $usedir -p $port -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+                    $pinfo = $data{tmp_psql};
+                    if ($pinfo !~ /ERROR/) {
+                        $info->{postgis}{$name} = $data{tmp_psql};
+                        $c->{gotpostgis}++;
+                        $data{gotpostgis}++;
+                    }
+                }
+            }
+            ## Others
+          CNAME:
+            my %contrib_modules = (
+                'citext'          => ['F:citext_smaller',        ''],
+                'cube'            => ['F:cube_distance',         ''],
+                'dblink'          => ['F:dblink_connect',        ''],
+                'earthdistance'   => ['F:earth_distance',        ''],
+                'fuzzystrmatch'   => ['F:dmetaphone_alt',        ''],
+                'hstore'          => ['F:hstore_out',            ''],
+                'intarray'        => ['F:intarray_del_elem',     ''],
+                'ltree'           => ['F:ltree2text',            ''],
+                'pgcrypto'        => ['F:gen_salt',              ''],
+                'pg_freespacemap' => ['F:pg_freespace',          ''],
+                'pgstattuple'     => ['F:pgstattuple',           ''],
+                'pg_trgm'         => ['F:show_trgm',             ''],
+                'tablefunc'       => ['F:crosstab4',             ''],
+                'tsearch2'        => ['F:to_tsvector',           '8.3'],
+                'uuid'            => ['F:uuid_ns_x500',          ''],
+            );
+
+          CNAME: for my $cname (sort keys %contrib_modules) {
+                my ($action,$version) = @{$contrib_modules{$cname}};
+                if ($action =~ s/F://) {
+                    $SQL = "SELECT count(*) FROM pg_proc WHERE proname = '$action'";
+                    run_command(qq{$PSQL -c "$SQL" --dbname "$qdb"}, 'tmp_psql');
+                    if ($data{tmp_psql} =~ /(\d+)/) {
+                        my $num = $1;
+                        if ($num) {
+                            if ($version =~ /(\d+)\.(\d+)/) {
+                                my ($maj,$min) = ($1,$2);
+                                my ($Maj,$Min) = ($c->{version}{major},$c->{version}{minor});
+                                next CNAME if $Maj > $maj or ($Maj==$maj and $Min >= $min);
+                            }
+                            $info->{contrib}{$cname} = 1;
+                            $c->{hascontrib}++;
+                        }
+                    }
+                }
+                else {
+                    die "Do not know how to handle action of $action for contrib module $cname!\n";
+                }
+            }
+        }
+
+
+        $opt{use_su_postgres} = 0;
+
+
+    } ## end each active port
+
+    return;
+
+} ## end of gather_postgresinfo
+
+
+sub gather_mysqlinfo {
+
+    return if $opt{nomysql} or ! exists $data{version}{mysql} or $data{version}{mysql} eq $UNKNOWN_VERSION;
+
+    run_command(q{mysqlshow --verbose}, 'tmp_mysqlshow');
+    my $info = $data{tmp_mysqlshow};
+
+    for my $line (split /\n/ => $info) {
+        next unless $line =~ m{^\| (.+)\s+\|\s+(\d+) \|$};
+        $data{mysql}{database}{$1} = {tables => $2};
+    }
+
+    run_command(q{mysql_config --port}, 'tmp_mysqlport');
+    $data{mysql}{port} = $data{tmp_mysqlport} =~ /(\d+)/ ? $1 : '?';
+
+    run_command(q{mysql_config --socket}, 'tmp_mysqlsocket');
+    $data{mysql}{socket} = $data{tmp_mysqlsocket} || '?';
+
+    run_command(q{mysqladmin status}, 'tmp_mysqladmin');
+    if ($data{tmp_mysqladmin} =~ /Uptime/) {
+        @{$data{mysql}{status}} = split /  / => $data{tmp_mysqladmin};
+    }
+
+    return;
+
+} ## end of gather_mysqlinfo
+
+
+sub skip_pg_database {
+
+    ## Return true if we should skip this database
+    ## Pass in the result from run_command
+
+    my $msg = shift;
+
+    return 0 if $msg !~ /ERROR/ and $msg !~ /FATAL/;
+
+    return 1 if $msg =~ /FATAL: SKIP/;
+
+    my $port = shift or die;
+    my $socket = shift or die;
+
+    if (exists $opt{skippgport}) {
+        if ($opt{skippgport} =~ /\b$port\b/) {
+            $verbose and warn "Skipping Postgres database port $port\n";
+            return 1;
+        }
+    }
+
+    my $line = (caller)[2];
+    warn qq{(Line $line) Connection to port $port, socket $socket failed: $msg\n};
+    warn qq{Perhaps skip this cluster with --skippgport=$port?\n};
+    return 1;
+
+} ## end of skip_pg_database
+
+
+sub pretty_size {
+
+    ## Transform number of bytes to a SI display similar to Postgres' format
+
+    my $bytes = shift;
+
+    return "$bytes bytes" if $bytes < 10240;
+
+    my @unit = qw/kB MB GB TB PB EB YB ZB/;
+
+    for my $p (1..@unit) {
+        if ($bytes <= 1024**$p) {
+            $bytes /= (1024**($p-1));
+            my $final = sprintf '%.2f %s', $bytes, $unit[$p-2];
+            $final =~ s/\.00 / /;
+            return $final;
+        }
+    }
+
+    return $bytes;
+
+} ## end of pretty_size
+
+
+sub pretty_conf {
+
+    ## Transform ugly configuration values to something better
+
+    my ($name,$val,$unit) = @_;
+
+    my $newval = $val;
+
+    return $newval if $unit eq '???';
+
+    ## -1 is always 'off'
+    if ('-1' eq $val) {
+        if ($name eq 'autovacuum_vacuum_cost_delay') {
+            $newval = '-1 (use vacuum_cost_delay)';
+        }
+        elsif ($name eq 'autovacuum_vacuum_cost_limit') {
+            $newval = '-1 (use vacuum_cost_limit)';
+        }
+        else {
+            $newval = '-1 (off)';
+        }
+    }
+    ## 0 can have a special meaning, but never gets expanded per below
+    elsif ('0' eq $val) {
+        if ($name eq 'log_temp_files') {
+            $newval = '0 (log all temporary files)';
+        }
+        elsif ($name eq 'log_min_duration_statement') {
+            $newval = '0 (log all durations)';
+        }
+        elsif ($name eq 'log_autovacuum_min_duration') {
+            $newval = '0 (log all autovac activity)';
+        }
+        elsif ($name eq 'archive_timeout' or $name eq 'checkpoint_warning') {
+            $newval = '0 (off)';
+        }
+        elsif ($name eq 'log_rotation_size' or $name eq 'log_rotation_age') {
+            $newval = '0 (no rotation)';
+        }
+        elsif ($name eq 'statement_timeout') {
+            $newval = '0 (disabled)';
+        }
+        else {
+            $newval = '0';
+        }
+    }
+    elsif ($unit eq 's' or $unit eq 'ms' or $unit eq 'min') {
+        if (0 == $val) {
+            $newval = '0';
+        }
+        else {
+            $newval = sprintf '%s (%s)', $val, pretty_time
+                ($unit eq 's' ? $val : $unit eq 'ms' ? ($val/1000) : ($val*60));
+        }
+    }
+    elsif ($unit eq '8kB') {
+        $newval = !$val ? $val : sprintf "$val (%s)", pretty_size($val*8192);
+    }
+    elsif ($unit eq 'kB') {
+        $newval = !$val ? $val : sprintf "$val (%s)", pretty_size($val*1024);
+    }
+
+    return $newval;
+
+} ## end of pretty_conf
+
+
+sub msg { ## no critic
+
+    my $name = shift || '?';
+
+    my $msg = '';
+
+    if (exists $msg{$lang}{$name}) {
+        $msg = $msg{$lang}{$name};
+    }
+    elsif (exists $msg{'en'}{$name}) {
+        $msg = $msg{'en'}{$name};
+    }
+    else {
+        my $line = (caller)[2];
+        die qq{Invalid message "$name" from line $line\n};
+    }
+
+    my $x=1;
+    {
+        my $val = $_[$x-1];
+        $val = '?' if ! defined $val;
+        last unless $msg =~ s/\$$x/$val/g;
+        $x++;
+        redo;
+    }
+    return $msg;
+
+} ## end of msg
+
+
+sub pretty_time {
+
+    ## Transform number of seconds to a more human-readable format
+    ## First argument is number of seconds
+    ## Second optional arg is highest transform: s,m,h,d,w
+    ## If uppercase, it indicates to "round that one out"
+
+    my $sec = shift;
+    my $tweak = shift || '';
+
+    ## Round to two decimal places, then trim the rest
+    $sec = sprintf '%.2f', $sec;
+    $sec =~ s/0+$//o;
+    $sec =~ s/\.$//o;
+
+    ## Just seconds (< 2:00)
+    if ($sec < 120 or $tweak =~ /s/) {
+        return sprintf "$sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+    }
+
+    ## Minutes and seconds (< 60:00)
+    if ($sec < 60*60 or $tweak =~ /m/) {
+        my $min = int $sec / 60;
+        $sec %= 60;
+        my $ret = sprintf "$min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+        $sec and $tweak !~ /S/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+        return $ret;
+    }
+
+    ## Hours, minutes, and seconds (< 48:00:00)
+    if ($sec < 60*60*24*2 or $tweak =~ /h/) {
+        my $hour = int $sec / (60*60);
+        $sec -= ($hour*60*60);
+        my $min = int $sec / 60;
+        $sec -= ($min*60);
+        my $ret = sprintf "$hour %s", $hour==1 ? msg('time-hour') : msg('time-hours');
+        $min and $tweak !~ /M/ and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+        $sec and $tweak !~ /[SM]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+        return $ret;
+    }
+
+    ## Days, hours, minutes, and seconds (< 28 days)
+    if ($sec < 60*60*24*28 or $tweak =~ /d/) {
+        my $day = int $sec / (60*60*24);
+        $sec -= ($day*60*60*24);
+        my $our = int $sec / (60*60);
+        $sec -= ($our*60*60);
+        my $min = int $sec / 60;
+        $sec -= ($min*60);
+        my $ret = sprintf "$day %s", $day==1 ? msg('time-day') : msg('time-days');
+        $our and $tweak !~ /H/     and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour')   : msg('time-hours');
+        $min and $tweak !~ /[HM]/  and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+        $sec and $tweak !~ /[HMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+        return $ret;
+    }
+
+    ## Weeks, days, hours, minutes, and seconds (< 28 days)
+    my $week = int $sec / (60*60*24*7);
+    $sec -= ($week*60*60*24*7);
+    my $day = int $sec / (60*60*24);
+    $sec -= ($day*60*60*24);
+    my $our = int $sec / (60*60);
+    $sec -= ($our*60*60);
+    my $min = int $sec / 60;
+    $sec -= ($min*60);
+    my $ret = sprintf "$week %s", $week==1 ? msg('time-week') : msg('time-weeks');
+    $day and $tweak !~ /D/      and $ret .= sprintf " $day %s", $day==1 ? msg('time-day')    : msg('time-days');
+    $our and $tweak !~ /[DH]/   and $ret .= sprintf " $our %s", $our==1 ? msg('time-hour')   : msg('time-hours');
+    $min and $tweak !~ /[DHM]/  and $ret .= sprintf " $min %s", $min==1 ? msg('time-minute') : msg('time-minutes');
+    $sec and $tweak !~ /[DHMS]/ and $ret .= sprintf " $sec %s", $sec==1 ? msg('time-second') : msg('time-seconds');
+
+    return $ret;
+
+} ## end of pretty_time
+
+
+sub ip2hostname {
+
+    my $ip = shift || '';
+
+    return if exists $data{ip}{$ip};
+
+    return if $opt{nohost};
+
+    if ($ip eq '127.0.0.1') {
+        $data{ip}{$ip} = 'localhost';
+        return;
+    }
+
+    run_command("host -r -t A $ip", "tmp_host_$ip");
+
+    $data{ip}{$ip} = $data{"tmp_host_$ip"};
+
+    if ($data{ip}{$ip} =~ /NXDOMAIN/) {
+        $data{ip}{$ip} = '';
+    }
+
+    return;
+
+} ## end of ip2hostname
+
+
+sub run_command {
+
+    ## Attempt to run a command and gather the input
+    ## We store the raw input, and store pretty info in the main hash
+
+    my $command = shift;
+    my $name = shift or die "Need a name!\n";
+    my $specifictimeout = shift || 0;
+
+    if ($opt{use_su_postgres} and $command =~ /^psql/) {
+        if ($command =~ s{-c "(.+?)"}{}) {
+            my $innercom = $1;
+            my $file = '/tmp/boxinfo.tmp';
+            open my $fh, '>', $file or die qq{Could not open "$file": $!\n};
+            print {$fh} $innercom;
+            close $fh or warn qq{Could not close "$file": $!\n};
+            $command = qq{su -l postgres --command "$command --file $file"};
+            $verbose and warn "New command: $command\n";
+        }
+    }
+
+    $verbose and warn "run_command $command => $name\n";
+
+    printf {$debugfh} "\nCOMMAND: %s\nNAME: $name\nTIME: %s\nRESULT: ", $command, scalar localtime();
+
+    local $ENV{LC_ALL} = 'C';
+
+    my $result;
+    my $madeit = 0;
+    alarm 0;
+    local $SIG{ALRM} = sub { die 'Timed out' };
+    my $localtimeout = $specifictimeout || $timeout;
+    eval {
+        alarm $localtimeout;
+        $result = qx{$command 2>&1};
+        $madeit = 1;
+    };
+    if ($@) {
+        if ($@ =~ /Timed out/o) {
+            warn "Command timed out at $localtimeout seconds!\n";
+            warn "Command: $command\n";
+            $madeit = 0;
+        }
+    }
+    alarm 0;
+    if (!$madeit) {
+        $data{$name} = '?';
+        $data{failed_command}{$command} = $@;
+        print {$debugfh} "FAIL ($@)\n";
+        return;
+    }
+    if (! defined $result)  {
+        $data{$name} = $UNKNOWN_VALUE;
+        print {$debugfh} "UNDEFINED\n";
+        return;
+    }
+
+    chomp $result;
+    $data{$name} = $result;
+    print {$debugfh} "OK $result\n";
+
+    $verbose and warn "Command finished OK\n";
+
+    return;
+
+} ## end of run_command
+
+
+sub create_html_output {
+
+    my $file = 'boxinfo.html';
+    open my $fh, '>', $file or die qq{Could not open "$file": $!\n};
+    my $oldselect = select $fh;
+
+    my @ip;
+    for my $int (sort keys %{$data{interface}}) {
+        my $d = $data{interface}{$int};
+        next unless exists $d->{UP} and exists $d->{RUNNING} and exists $d->{address};
+        my $ip = $d->{address};
+        my $host = $data{ip}{$ip} || $UNKNOWN_VALUE;
+        push @ip, qq{<b>$ip</b> $host};
+    }
+    my $iplist = join "<br />\n" => @ip;
+
+    if ('html' eq $format) {
+        print qq{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<title>boxinfo: Gather system information</title>
+<meta http-equiv="content-type" content="text/html;charset=utf-8" />
+<meta name="generator" content="boxinfo $VERSION" />
+
+<style type="text/css">
+table.boxinfo th { color: #006600; vertical-align: top; padding-top: 7px; padding-bottom: 15px; text-align: left; padding-left: 5px; padding-right: 5px; }
+
+table { empty-cells: show; margin-bottom: 20px; }
+
+table.bottom { margin-bottom: 30px; }
+
+table.boxinfo2 td { padding-left: 5px; padding-right: 5px; padding-top: 7px; padding-bottom: 15px; vertical-align: middle;}
+
+table.boxinfo td { padding-left: 2px; padding-right: 2px; vertical-align: middle; font-weight: bold;}
+
+table.boxinfo td.numbers { color: black; text-align: right; }
+
+table.plain { border-collapse: collapse; }
+table.plain td { font-weight: normal; border: solid #000000 1px; padding: 5px; }
+table.plain th { border: solid #000000 1px; padding: 5px; }
+
+table.boxinfo td.activeip { color: black; font-weight: bolder; }
+
+</style>
+</head>
+<body>};
+
+    }
+
+    if ('wiki' eq $format) {
+        my $gotpg = exists $data{postgres}{active_port} ? 1 : 0;
+        printf qq{[[Category:%s servers]]\n\n},
+            $gotpg ? 'Postgres' : 'Client';
+        printf qq{<h2>[[%s]] %s %s:</h2>\n\n},
+            $data{clientname},
+                $gotpg ? 'Postgres server' : 'server',
+                    $data{shorthost};
+    }
+
+    my $cols = 1;
+    print qq{<table class="boxinfo" style="border: solid black 2px">\n};
+
+    print qq{<tr><th>Date gathered:</th><td><b>$data{program_start}</b></td></tr>\n};
+
+    print qq{<tr><th>Program version:</th><td><b>$data{program_version}</b></td></tr>\n};
+
+    print qq{<tr><th>Hostname:</th><td><b>$data{hostname}</b></td></tr>\n};
+
+    print qq{<tr><th>OS:</th><td><b>$data{OS}</b></td></tr>\n};
+
+    if ($opt{postgresonly}) {
+        html_postgres();
+        goto HTMLEND;
+    }
+
+    html_cpus();
+
+    print qq{<tr><th>RAM:</th><td><b>$data{memory}{pretty}{Total}</b></td></tr>\n};
+
+    html_vm();
+
+    html_rightscale();
+
+    html_ec2();
+
+    html_uptime();
+
+    html_lsb();
+
+    html_dist();
+
+    html_puppet();
+
+    html_kernel();
+
+    html_shared_memory();
+
+    html_shared_active();
+
+    html_hugepages();
+
+    html_lifekeeper();
+
+    html_heartbeat();
+
+    html_interfaces();
+
+    html_routes();
+
+    html_fs();
+
+    html_queues();
+
+    html_disk_settings();
+
+    html_versions();
+
+    html_perlinfo();
+
+    html_perlmodules();
+
+    html_gems();
+
+    html_chkconfig();
+
+    html_postgres();
+
+    html_mysql();
+
+    html_ulimits();
+
+    html_envs();
+
+    html_yum();
+
+    html_aptitude();
+
+    HTMLEND:
+
+print q{
+</table>
+};
+    if ('html' eq $format) {
+        print q{
+</body>
+</html>};
+       }
+
+    select $oldselect;
+    close $fh or die qq{Could not close "$file": $!\n};
+
+    print "Wrote $file\n";
+
+    return;
+
+} ## end of create_html_output
+
+
+sub html_postgres {
+    html_postgres_active();
+    html_postgres_recovery();
+    html_postgres_problems();
+    html_postgres_config();
+    html_postgres_databases();
+    html_postgres_tablespaces();
+    html_postgres_slony();
+    html_postgres_bucardo();
+    html_postgres_postgis();
+    html_postgres_pgconfig();
+    return;
+} ## end of html_postgres
+
+
+sub html_cpus {
+
+    return if ! exists $data{cpuinfo};
+
+    ## CPUs may be different - if so, list individually
+    ## If not, just say <number> x <info>
+
+    my $allthesame = 1;
+    my %cinfo;
+  CPU: for my $cpu (values %{$data{cpuinfo}}) {
+        for my $item ('model name', 'cache size', 'speed', 'cpu cores') {
+            next if ! exists $cpu->{$item};
+            $cpu->{$item} =~ s/\s+/ /g;
+            if (! exists $cinfo{$item}) {
+                $cinfo{$item} = $cpu->{$item};
+            }
+            elsif ($cpu->{$item} ne $cinfo{$item}) {
+                $allthesame=0;
+                last CPU;
+            }
+        }
+    }
+
+
+    if ($allthesame) {
+        my $cache = exists $cinfo{'cache size'} ? " Cache size: $cinfo{'cache size'}" : '';
+        print qq{<tr><th>CPU:</th><td><b>$data{numcpus} x $cinfo{'model name'} ($cinfo{speed})$cache</b></td></tr>\n};
+        return;
+    }
+
+    my @cpulist;
+    for my $num (sort { $a <=> $b } keys %{$data{cpuinfo}}) {
+        my $cpu = $data{cpuinfo}{$num};
+        my $cache = exists $cpu->{'cache size'} ? " Cache size: $cpu->{'cache size'}" : '';
+        push @cpulist => "CPU $num: $cpu->{'model name'} ($cpu->{speed})$cache";
+    }
+    my $cpulist = join '<br />' => @cpulist;
+    print qq{<tr><th>CPUs:</th><td><b>$cpulist</b></td></tr>\n};
+
+    return;
+
+} ## end of html_cpus
+
+
+sub html_vm {
+
+    return if ! exists $data{VM};
+
+    print qq{<tr><th>VM:</th><td><b>$data{VM}</b></td></tr>\n\n};
+
+    return;
+
+} ## end of html_vm
+
+
+sub html_rightscale {
+
+    return if ! exists $data{RightScale};
+
+    ## We assume a version is always provided
+    ## The cloud, however, may not be there
+    print qq{<tr><th>RightScale:</th><td>Release <b>$data{RightScale}{version}</b>};
+
+    if (exists $data{RightScale}{cloud}) {
+        printf ' Cloud: %s',
+            (defined $data{RightScale}{cloud} and length $data{RightScale}{cloud})
+            ? "<b>$data{RightScale}{cloud}</b>" : '?';
+    }
+    print qq{</td></tr>\n\n};
+
+    return;
+
+} ## end of html_rightscale
+
+sub html_ec2 {
+
+    return if ! exists $data{EC2};
+
+    print qq{<tr><th$vtop>${wrap}EC2:</th><td><br /><table border="1" style="border: black solid 1px">};
+    for my $name (sort keys %{$data{EC2}}) {
+        next if $name eq 'meta';
+        print qq{<tr><td>$name: </td><td><b>$data{EC2}{$name}</b></td></tr>\n};
+    }
+    if (exists $data{EC2}{meta}) {
+        for my $name (sort keys %{$data{EC2}{meta}}) {
+            my $value = $data{EC2}{meta}{$name};
+            if (ref $value eq 'HASH') {
+                $value = join '<br />'
+                       => map { "$_ = $data{EC2}{meta}{$name}{$_}" }
+                       sort keys %{$data{EC2}{meta}{$name}};
+                $name =~ s{/$}{};
+            }
+            else {
+                $value =~ s{\n}{<br />\n}g;
+            }
+        print qq{<tr><td>$name: </td><td><b>$value</b></td></tr>\n};
+        }
+    }
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_ec2
+
+
+sub html_uptime {
+
+    if (exists $data{uptime}) {
+        print qq{<tr><th>Uptime:</th><td><b>$data{uptime}</b></td></tr>\n\n};
+    }
+
+    if (exists $data{users_logged_in}) {
+        print qq{<tr><th>Users:</th><td><b>$data{users_logged_in}</b></td></tr>\n\n};
+    }
+
+    if (exists $data{load_average}) {
+        print qq{<tr><th>Load average:</th><td><b>$data{load_average}</b></td></tr>\n\n};
+    }
+
+    return;
+
+} ## end of html_uptime
+
+
+sub html_lsb {
+
+    return if ! exists $data{lsb_release};
+
+    print qq{<tr><th$vtop>${wrap}LSB info:</th><td><br /><table class="plain">};
+    for my $name (sort keys %{$data{lsb_release}}) {
+        print qq{<tr><td>$name: </td><td><b>$data{lsb_release}{$name}</b></td></tr>\n};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_lsb
+
+
+sub html_dist {
+
+    for my $dist (keys %distlist) {
+        next if ! exists $data{dist}{$dist};
+        my $ver = $data{dist}{$dist};
+        next if $dist eq 'redhat' and exists $data{dist}{fedora} and $ver =~ /Fedora/;
+        if ($distlist{$dist}->[2] eq 'version' and exists $data{dist_codename}) {
+            $ver .= " ($data{dist_codename})";
+        }
+        printf qq{<tr><th>%s %s:</th><td><b>%s</b></td></tr>\n\n},
+            $distlist{$dist}->[1], $distlist{$dist}->[2], $ver;
+    }
+
+    return;
+
+} ## end of html_dist
+
+
+sub html_puppet {
+
+    return if ! exists $data{puppet};
+
+    print q{<tr><th>Puppet classes:</th><td><br /><table class="plain">};
+    for my $name (@{$data{puppet}{class}}) {
+        print qq{<tr><td><b>$name</b></td></tr>\n};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_puppet
+
+
+sub html_kernel {
+
+    return if !exists $data{'Kernel name'};
+
+    print qq{<tr><th$vtop>${wrap}Kernel info:</th><td><br /><table class="plain">};
+    for my $name ('Kernel name', 'Kernel release', 'Kernel version', 'Hardware name', 'Processor', 'Hardware platform') {
+        next if ! exists $data{$name} or $data{$name} eq 'unknown' or $data{$name} =~ /--help/;
+        print qq{<tr><td>$name: </td><td><b>$data{$name}</b></td></tr>};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_kernel
+
+
+sub html_shared_memory {
+
+    return if ! exists $data{memory}{shmmax};
+
+    print qq{<tr><th$vtop>${wrap}Memory:</th><td><br /><table class="plain">};
+    print qq{<tr><td>shmmax: </td><td style="text-align: right"><b>$data{memory}{pretty}{shmmax}</b></td></tr>\n};
+    print qq{<tr><td>shmmni: </td><td style="text-align: right"><b>$data{memory}{shmmni}</b></td></tr>\n};
+    print qq{<tr><td>shmall: </td><td style="text-align: right"><b>$data{memory}{pretty}{shmall}</b></td></tr>\n};
+
+    for my $vm ('swappiness','dirty_ratio','dirty_background_ratio') {
+        if (exists $data{vm}{$vm}) {
+            print qq{<tr><td>$vm: </td><td style="text-align: right"><b>$data{vm}{$vm}</b></td></tr>\n};
+        }
+    }
+
+    for my $m ('Free', 'Cached', 'Active', 'Swap Total') {
+        print qq{<tr><td>$m: </td><td style="text-align: right"><b>$data{memory}{pretty}{$m}</b></td></tr>\n};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_shared_memory
+
+
+sub html_shared_active {
+
+    return if !exists $data{memory}{active_shared};
+
+    print qq{<tr><th$vtop>${wrap}Active shared mem:</th><td><br /><table class="plain">};
+    print qq{<tr><td>Active segments: </td><td style="text-align: right"><b>$data{memory}{active_shared}</b></td></tr>\n};
+    print qq{<tr><td>Active semaphores: </td><td style="text-align: right"><b>$data{memory}{active_semaphores}</b></td></tr>\n};
+    print qq{<tr><td>Active messages: </td><td style="text-align: right"><b>$data{memory}{active_messages}</b></td></tr>\n};
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_shared_active
+
+
+sub html_hugepages {
+
+    return if ! exists $data{memory}{transparent_hugepages};
+
+    print qq{<tr><th$vtop>${wrap}Huge pages:</th><td><br /><table class="plain">};
+    print qq{<tr><td>Huge Page size: </td><td style="text-align: right"><b>$data{memory}{pretty}{HPsize}</b></td></tr>\n};
+    my $total_huge_pretty = pretty_size($data{memory}{HPtotal} * $data{memory}{HPsize});
+    print qq{<tr><td>Huge Pages total: </td><td style="text-align: right"><b>$data{memory}{HPtotal}</b> ($total_huge_pretty)</td></tr>\n};
+    my $free_huge_pretty = pretty_size($data{memory}{HPfree} * $data{memory}{HPsize});
+    print qq{<tr><td>Huge Pages free: </td><td style="text-align: right"><b>$data{memory}{HPfree}</b> ($free_huge_pretty)</td></tr>\n};
+    my $reserved_huge_pretty = pretty_size($data{memory}{HPreserved} * $data{memory}{HPsize});
+    print qq{<tr><td>Huge Pages reserved: </td><td style="text-align: right"><b>$data{memory}{HPreserved}</b> ($reserved_huge_pretty)</td></tr>\n};
+    print qq{<tr><td>Transparent hugepages: </td><td style="text-align: right"><b>$data{memory}{transparent_hugepages}</b></td></tr>\n};
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+
+
+
+} ## end of html_hugepages
+
+
+sub html_lifekeeper {
+
+    return if !exists $data{lifekeeper};
+
+    print qq{<tr><th$vtop>${wrap}Lifekeeper services:</th><td><br /><table class="plain">\n};
+    print qq{<tr><th>Service</th><th>Primary host</th><th>State</th></tr>\n};
+    my $s = $data{lifekeeper}{service};
+    for my $row (sort { $s->{$b}{prio} <=> $s->{$a}{prio} } keys %$s) {
+        printf "<tr><td><b>$row</b></td><td>%s</td><td>%s</td></tr>\n",
+            $s->{$row}{primary}, $s->{$row}{state};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_lifekeeper
+
+
+sub html_heartbeat {
+
+    return if !exists $data{heartbeat};
+
+    print qq{<tr><th$vtop>${wrap}Linux HA:</th><td><br /><table class="plain">\n};
+
+    if (exists $data{heartbeat}{active}) {
+        print qq{<tr><th colspan="2">$data{heartbeat}{active}</tr>\n};
+    }
+
+    if (exists $data{heartbeat}{haresources}) {
+    my $har = escape_html($data{heartbeat}{haresources});
+        print qq{<tr><td>haresources:</td><td>$har</td></tr>\n};
+    }
+
+    if (exists $data{heartbeat}{node}) {
+        print q{<tr><td>Nodes:</td><td><b>};
+        print join '<br />' => @{$data{heartbeat}{node}};
+        print qq{</b></td></tr>\n};
+    }
+
+    if (exists $data{heartbeat}{config}) {
+        my $config = $data{heartbeat}{config};
+        $config =~ s/\s*$//gm;
+        print qq{<tr><td>Config:</td><td><pre>$config</pre></td></tr>\n};
+    }
+
+    if (exists $data{heartbeat}{resources}) {
+        my $reslist = join '<br />' => sort @{$data{heartbeat}{resources}};
+        print qq{<tr><td>Resources:</td><td>$reslist</td></tr>\n};
+    }
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_heartbeat
+
+
+sub html_interfaces {
+
+    return if ! exists $data{interface};
+
+    my $gotnotes = 0;
+    for my $int (keys %{$data{interface}}) {
+        $gotnotes = 1 if exists $data{interface}{$int}{POINTOPOINT};
+    }
+
+    print qq{<tr><th$vtop>${wrap}Interfaces:</th><td><br /><table class="plain">\n};
+    printf q{<tr><th>Name</th><th>IP</th><th>Status</th><th>Speed</th>%s</tr>},
+        $gotnotes ? q{<th>Notes</th>} : '';
+    my $x;
+    for my $int (map { $_->[0] }
+                 sort { $b->[1] <=> $a->[1] or $b->[2] cmp $a->[2] or $a->[0] cmp $b->[0] }
+                 map { $x=$data{interface}{$_}; [$_, exists $x->{RUNNING} ? 1 : 0, $x->{address} || 0] }
+                 keys %{$data{interface}}) {
+        my $d = $data{interface}{$int};
+        my $ip = $d->{RUNNING} ? ($d->{address} || $d->{inet6_address} || '-') : 'N/A';
+        printf q{<tr><td%s>%s</td><td>%s</td><td>%s</td>},
+            exists $d->{RUNNING} ? q{ class='activeip'} : '',
+                exists $d->{RUNNING} ? qq{<b>$int</b>} : $int,
+                    $ip,
+                        exists $d->{RUNNING} ? 'Active' : 'Inactive';
+        printf '<td>%s',
+            exists $d->{current_speed} ? "$d->{current_speed} $d->{duplex}" : exists $d->{RUNNING} ?
+                $int =~ /^lo|tun|bond/ ? 'N/A' : $UNKNOWN_VALUE : 'N/A';
+        if (exists $d->{nowspeed} and exists $d->{maxspeed} and $d->{maxspeed} and ($d->{nowspeed} < $d->{maxspeed})) {
+            print " <b>(but max is $d->{maxspeed})</b>";
+        }
+        print '</td>';
+        if ($gotnotes) {
+            printf '<td>%s</td>',
+                exists $d->{POINTOPOINT} ? 'point to point' : ' &nbsp;';
+        }
+        print "</tr>\n";
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_interfaces
+
+
+sub html_routes {
+
+    return if ! exists $data{route};
+
+    print qq{<tr><th$vtop>${wrap}Routes:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Destination</th><th>Gateway</th><th>Genmask</th><th>Interface</th></tr>};
+    for my $r (@{$data{route}}) {
+        print qq{<tr><td>$r->{dest}</td><td>$r->{gateway}</td><td>$r->{mask}</td><td>$r->{int}</td></tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_routes
+
+
+sub html_fs {
+
+    return if ! exists $data{fs};
+
+    print qq{<tr><th$vtop>${wrap}Mounting info:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Name</th><th>Mount</th><th>Size/Used %</th><th>Inode %</th><th>Filesystem (options)</th></tr>};
+    for my $fs (sort keys %{$data{fs}}) {
+        my $d = $data{fs}{$fs};
+        next if ! defined $d->{mounted};
+        next if $fs eq 'none';
+        (my $ffs = escape_html($fs)) =~ s{(.+/)([\w\-]{20,})$}{$1<br />$2};
+        $ffs =~ s{^([^:]{15,}:)(.+)}{$1<br />$2};
+        print qq{<tr><td>$ffs</td><td><b>$d->{mounted}</b></td><td>$d->{size}/$d->{capacity}</td>};
+        print qq{<td>$d->{inodes_usage}</td>\n};
+        $d->{options} = '' if ! exists $d->{options};
+        my $options = $d->{options};
+        if (length $options > 20) {
+            $options =~ s{,}{,<br />}g;
+        }
+        printf qq{<td>%s%s</td></tr>\n},
+            defined $d->{type} ? $d->{type} : '?',
+            $options ? " ($options)" : '';
+    }
+
+    if (exists $data{mdstat}) {
+        printf q{<tr><td>mdstat:</td><td colspan='4'>%s</td></tr>},
+            escape_html($data{mdstat});
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_fs
+
+
+sub html_queues {
+
+    return if ! exists $data{block};
+
+    print qq{<tr><th$vtop>${wrap}Kernel queue info:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Block</th><th>Read ahead size</th><th>Scheduler</th></tr>};
+    for my $name (sort keys %{$data{block}}) {
+        my $q = $data{block}{$name};
+        $q->{scheduler} =~ s{(\[\w+\])}{<b>$1</b>};
+        print qq{<tr><td>$name</td><td>$q->{readahead}</td><td>$q->{scheduler}</td></tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_queues
+
+
+sub html_disk_settings {
+
+    return if ! exists $data{disk};
+
+    print qq{<tr><th$vtop>${wrap}Disk information:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Name</th><th>Read ahead size</th><th>Scheduler</th></tr>};
+    for my $fs (sort keys %{$data{fs}}) {
+        my $d = $data{fs}{$fs};
+        next if ! defined $d->{mounted};
+        next if $fs eq 'none';
+        print qq{<tr><td>$fs</td><td><b>$d->{mounted}</b></td><td>$d->{size}/$d->{capacity}</td>};
+        print qq{<td>$d->{inodes_usage}</td>\n};
+        print qq{<td>$d->{readahead}</td>\n};
+        print qq{<td>$d->{scheduler}</td>\n};
+        print qq{<td>$d->{type} ($d->{options})</td></tr>\n};
+    }
+
+    if (exists $data{mdstat}) {
+        print q{<tr><td>mdstat:</td><td colspan=5>%s</td></tr>},
+            escape_html($data{mdstat});
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_disk_settings
+
+
+sub html_versions {
+
+    return if ! exists $data{version};
+
+    my $TWOWRAP = 15;
+    my $THREEWRAP = 30;
+
+    my $count = 0;
+    for my $v (keys %{$data{version}}) {
+        $count++ if $data{version}{$v} ne '?';
+    }
+    return if ! $count;
+
+    print qq{<tr><th$vtop>${wrap}Program versions:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Name</th><th>Version</th>};
+    if ($count >= $TWOWRAP) {
+        print q{<th>Name</th><th>Version</th>};
+    }
+    if ($count >= $THREEWRAP) {
+        print q{<th>Name</th><th>Version</th>};
+    }
+    print qq{</tr>\n};
+
+    my $offset = 0;
+    for my $prog (sort keys %{$data{version}}) {
+        my $v = $data{version}{$prog};
+        next if $v eq '?';
+        if ($count < $TWOWRAP) {
+            printf qq{<tr><td>$prog</td><td>%s</td></tr>\n},
+                $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>};
+        }
+        elsif (!$offset) {
+            printf qq{<tr><td>$prog</td><td>%s</td>},
+                $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>&nbsp;};
+            $offset = 1;
+        }
+        elsif ($count >= $THREEWRAP and $offset < 2) {
+            printf qq{<td>$prog</td><td>%s</td>},
+                $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>&nbsp;};
+            $offset = 2;
+        }
+        else {
+            printf qq{<td>$prog</td><td>%s</td></tr>},
+                $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>};
+            $offset = 0;
+        }
+    }
+    if ($offset) {
+        print q{<td>&nbsp;</td><td>&nbsp;</td>};
+        $offset==1 and $count >= $THREEWRAP and print q{<td>&nbsp;</td><td>&nbsp;</td>};
+        print qq{</tr>\n};
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_versions
+
+
+sub html_perlinfo {
+
+    return if ! exists $data{perl};
+
+    ## Where installed?
+
+    print qq{<tr><th$vtop>${wrap}Perl info:</th><td><br /><table class="plain">\n};
+    print qq{<tr><td>Version:</td><td><b>$data{version}{perl}</b></td></tr>\n};
+    ## Only one of these is needed
+    (my $pver = $data{version}{perl}) =~ s/^(\d+\.\d+)\..*/$1/e;
+    if ($pver < 5.10) {
+        print qq{<tr><td>Threads:</td><td><b>$data{perl}{threads}</b></td></tr>\n};
+    }
+    else {
+        print qq{<tr><td>IThreads:</td><td><b>$data{perl}{ithreads}</b></td></tr>\n};
+    }
+    print qq{<tr><td>Multiplicity:</td><td><b>$data{perl}{multiplicity}</b></td></tr>\n};
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_perlinfo
+
+
+sub html_perlmodules {
+
+    return if ! exists $data{perlmodver};
+
+    print qq{<tr><th$vtop>${wrap}Perl modules:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Module</th><th>Version</th></tr>};
+    my $BASE = 'http://search.cpan.org/dist';
+    for my $mod (sort keys %{$data{perlmodver}}) {
+        my $v = $data{perlmodver}{$mod};
+        (my $safename = $mod) =~ s/::/-/g;
+        my $url = "$BASE/$safename";
+        if ('wiki' eq $format) {
+            printf qq{<tr><td>[$url $mod]</td><td>%s</td></tr>\n},
+            $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>};
+        }
+        else {
+            printf qq{<tr><td><a href="$url">$mod</a></td><td>%s</td></tr>\n},
+                $v eq '?' ? $UNKNOWN_VALUE : qq{<b>$v</b>};
+        }
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_perlmodules
+
+
+sub html_chkconfig {
+
+    return if ! exists $data{chkconfig};
+
+    my $TWOWRAP = 10;
+    my $THREEWRAP = 20;
+
+    print qq{<tr><th$vtop>${wrap}Started via chkconfig:</th><td><br /><table class="plain">\n};
+    my $count = keys %{$data{chkconfig}};
+    my $offset = 0;
+    for my $prog (sort keys %{$data{chkconfig}}) {
+        if ($count < $TWOWRAP) {
+            print qq{<tr><td>$prog</td></tr>\n};
+        }
+        elsif (!$offset) {
+            print qq{<tr><td>$prog</td>};
+            $offset = 1;
+        }
+        elsif ($count >= $THREEWRAP and $offset < 2) {
+            print qq{<td>$prog</td>};
+            $offset = 2;
+        }
+        else {
+            print qq{<td>$prog</td></tr>\n};
+            $offset = 0;
+        }
+    }
+    if ($offset) {
+        print q{<td>&nbsp;</td>};
+        $offset==1 and $count >= $THREEWRAP and print q{<td>&nbsp;</td>};
+        print qq{</tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_chkconfig
+
+
+sub html_postgres_active {
+
+    ## Show active Postgres cluster ports, socket dirs, and version
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{active_port};
+
+    print qq{<tr><th$vtop>${wrap}Active Postgres clusters:</th><td><br /><table class="plain">\n};
+
+    my ($extrasym, $extranotes) = (0,0);
+    for my $cluster (keys %{$data{postgres}{active_port}}) {
+        if (exists $data{postgres}{active_port}{$cluster}{symlink}) {
+            $extrasym = 1;
+        }
+        if (exists $data{postgres}{active_port}{$cluster}{recfile}
+            or exists $data{postgres}{active_port}{$cluster}{startingup}) {
+            $extranotes = 1;
+        }
+    }
+
+    print q{<tr><th>Port</th><th>Socket dir</th><th>Version</th>};
+    $extrasym and print q{<th>Symlinks</th>};
+    $extranotes and print q{<th>Notes</th>};
+    print qq{</tr>\n};
+    for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $c = $data{postgres}{active_port}{$cluster};
+        next if $c->{deleted};
+        my $port = $c->{port};
+        my $dir = $c->{largesocketdir} || $c->{socketdir};
+        my $ver = $c->{setting}{server_version}{value} || $c->{version}{full} || '?';
+        if ($ver =~ /^\d+\.\d+$/ and exists $data{version}{psql}) {
+            $ver .= "<br />(probably $data{version}{psql})";
+        }
+        print qq{<tr><td><b>$port</b></td><td><b>$dir</b></td><td><b>$ver</b></td>};
+        if ($extrasym) {
+            my $info = '&nbsp;';
+            if (exists $c->{symlink}) {
+                $info = join '<br />' =>
+                    map { "$_ -> $c->{symlink}{$_}" }
+                    sort
+                    keys %{$c->{symlink}};
+            }
+            print "<td>$info</td>";
+        }
+        if ($extranotes) {
+            my $info = '&nbsp;';
+            if (exists $c->{recfile}) {
+                $info = 'In recovery mode';
+            }
+            elsif (exists $c->{startingup}) {
+                $info = 'Starting up';
+            }
+            if (exists $c->{datadirsize}) {
+                $info .= "<br />DB size: $c->{datadirsize}";
+            }
+            print "<td>$info</td>";
+        }
+        print "</tr>\n";
+    }
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_postgres_active
+
+
+sub html_postgres_config {
+
+    ## Show active cluster configuration settings (interesting ones only)
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{active_port};
+
+    ## Make sure we have at least one (may not due to warm standby servers, etc.)
+    my $found = 0;
+    for my $cluster (keys %{$data{postgres}{active_port}}) {
+        if (exists $data{postgres}{active_port}{$cluster}{setting}{lc_time}) {
+            $found = 1;
+            last;
+        }
+    }
+    return if !$found;
+
+    print qq{<tr><th$vtop>${wrap}&rarr; Postgres config:</th><td><br /><table class="plain">\n};
+
+    ## We don't want to report on all config values, just interesting ones
+    ## A = always show
+    ## D = show only if different
+    ## S = skip it
+
+my $pg_settings = q{
+D add_missing_from                | off               
+D allow_system_table_mods         | off               
+S application_name                |                   
+D archive_command                 | (disabled)        
+D archive_mode                    | off               
+D archive_timeout                 | 0                 
+D array_nulls                     | on                
+D authentication_timeout          | 60                
+A autovacuum                      |                   
+D autovacuum_analyze_scale_factor | 0.1               
+D autovacuum_analyze_threshold    | 50                
+D autovacuum_freeze_max_age       | 200000000         
+D autovacuum_max_workers          | 3                 
+D autovacuum_naptime              | 60                
+D autovacuum_vacuum_cost_delay    | 20                
+D autovacuum_vacuum_cost_limit    | -1                
+D autovacuum_vacuum_scale_factor  | 0.2               
+D autovacuum_vacuum_threshold     | 50                
+D backslash_quote                 | safe_encoding     
+D bgwriter_delay                  | 200               
+D bgwriter_lru_maxpages           | 100               
+D bgwriter_lru_multiplier         | 2                 
+D block_size                      | 8192              
+D bounjour                        | off               
+D bonjour_name                    |                   
+D bytea_output                    | hex               
+D check_function_bodies           | on                
+D checkpoint_completion_target    | 0.5               
+A checkpoint_segments             | 3                 
+D checkpoint_timeout              | 300               
+D checkpoint_warning              | 30                
+A client_encoding                 | UTF8              
+D client_min_messages             | notice            
+D commit_delay                    | 0                 
+D commit_siblings                 | 5                 
+A config_file                     |                   
+D constraint_exclusion            | off               
+D cpu_index_tuple_cost            | 0.005             
+D cpu_operator_cost               | 0.0025            
+D cpu_tuple_cost                  | 0.01              
+D cursor_tuple_fraction           | 0.1               
+D custom_variable_classes         |                   
+A data_directory                  |                   
+D DateStyle                       | ISO, MDY          
+D db_user_namespace               | off               
+D deadlock_timeout                | 1000              
+D debug_assertions                | off               
+D debug_pretty_print              | off               
+D debug_print_parse               | off               
+D debug_print_plan                | off               
+D debug_print_rewritten           | off               
+A default_statistics_target       | 10 or 100         
+D default_tablespace              |                   
+D default_text_search_config      | pg_catalog.english
+D default_transaction_isolation   | read committed    
+D default_transaction_read_only   | off               
+D default_with_oids               | off               
+D dynamic_library_path            | $libdir           
+A effective_cache_size            |                   
+D effective_io_concurrency        | 1                 
+D enable_bitmapscan               | on                
+D enable_hashagg                  | on                
+D enable_hashjoin                 | on                
+D enable_indexscan                | on                
+D enable_material                 | on
+D enable_mergejoin                | on                
+D enable_nestloop                 | on                
+D enable_seqscan                  | on                
+D enable_sort                     | on                
+D enable_tidscan                  | on                
+D escape_string_warning           | on                
+D exit_on_error                   | off
+D explain_pretty_print            | on                
+D external_pid_file               |                   
+D extra_float_digits              | 0                 
+D from_collapse_limit             | 8                 
+D fsync                           | on                
+D full_page_writes                | on                
+D geqo                            | on                
+D geqo_effort                     | 5                 
+D geqo_generations                | 0                 
+D geqo_pool_size                  | 0                 
+D geqo_seed                       | 0                 
+D geqo_selection_bias             | 2                 
+D geqo_threshold                  | 12                
+D gin_fuzzy_search_limit          | 0                 
+A hba_file                        |    
+D hot_standby                     | off
+D hot_standby_feedback            | off              
+A ident_file                      |                   
+D ignore_system_indexes           | off               
+A integer_datetimes               | on               
+D IntervalStyle                   | postgres          
+D join_collapse_limit             | 8                 
+S krb_caseins_users               | off               
+S krb_realm                       |                   
+S krb_server_hostname             |                   
+S krb_server_keyfile              |                   
+S krb_srvname                     | postgres          
+S lc_collate                      |                   
+S lc_ctype                        |                   
+S lc_messages                     |                   
+S lc_monetary                     |                   
+S lc_numeric                      |                   
+S lc_time                         |                   
+D listen_addresses                | *                 
+D lo_compat_privileges            | off               
+D local_preload_libraries         |                   
+D log_autovacuum_min_duration     | -1                
+D log_checkpoints                 | off               
+D log_connections                 | off               
+A log_destination                 | stderr            
+D log_directory                   | pg_log            
+D log_disconnections              | off               
+D log_duration                    | off               
+D log_error_verbosity             | default           
+D log_executor_stats              | off               
+D log_file_mode                   | 0600                   
+A log_filename                    |                   
+D log_hostname                    | off               
+D log_line_prefix                 |                   
+D log_lock_waits                  | off               
+A log_min_duration_statement      | -1                
+D log_min_error_statement         | error             
+D log_min_messages                | warning           
+D log_parser_stats                | off               
+D log_planner_stats               | off               
+D log_rotation_age                | 1440              
+D log_rotation_size               | 10240             
+D log_statement                   | none              
+D log_statement_stats             | off               
+D log_temp_files                  | -1                
+D log_timezone                    | US/Eastern        
+D log_truncate_on_rotation        | off               
+A logging_collector               | off               
+A maintenance_work_mem            | 16384             
+A max_connections                 |                   
+D max_files_per_process           | 1000              
+A max_fsm_pages                   |                   
+D max_fsm_relations               | 1000              
+D max_function_args               | 100               
+D max_identifier_length           | 63                
+D max_index_keys                  | 32                
+D max_locks_per_transaction       | 64                
+D max_pred_locks_per_transaction  | 64                
+A max_prepared_transactions       | 5                 
+D max_stack_depth                 | 2048              
+D max_standby_archive_delay       | 30000                
+D max_standby_streaming_delay     | 30000
+A max_wal_senders                 | 0                
+D password_encryption             | on                
+D port                            | 5432              
+D post_auth_delay                 | 0                 
+D pre_auth_delay                  | 0                 
+D quote_all_identifiers           | off 
+A random_page_cost                |                   
+D replication_timeout             | 60000
+D restart_after_crash             | on
+D regex_flavor                    | advanced          
+D search_path                     | "$user",public    
+D segment_size                    | 131072            
+D seq_page_cost                   | 1                 
+A server_encoding                 | UTF8              
+S server_version                  | 8.something       
+S server_version_num              | 80303             
+D session_replication_role        | origin            
+A shared_buffers                  | 3072              
+D shared_preload_libraries        |                   
+D silent_mode                     | off               
+D sql_inheritance                 | on                
+D ssl                             | off               
+S ssl_ciphers                     |                
+S ssl_renegotiation_limit         | 524288               
+D standard_conforming_strings     | off               
+D statement_timeout               | 0                 
+D stats_temp_directory            | pg_stat_tmp       
+D superuser_reserved_connections  | 3                 
+D synchronize_seqscans            | on                
+D synchronous_commit              | on 
+D synchronous_standby_names       |                
+D syslog_facility                 | LOCAL0            
+D syslog_ident                    | postgres          
+D tcp_keepalives_count            | 0                 
+D tcp_keepalives_idle             | 0                 
+D tcp_keepalives_interval         | 0                 
+D temp_buffers                    | 1024              
+D temp_tablespaces                |                   
+S TimeZone                        | US/Eastern        
+D timezone_abbreviations          | Default           
+D trace_notify                    | off               
+D trace_recovery_messages         | debug1            
+D trace_sort                      | off               
+D track_activities                | on                
+D track_activity_query_size       | 1024              
+D track_counts                    | on                
+D track_functions                 | none 
+D transaction_defferable          | off              
+D transaction_isolation           | read committed    
+D transaction_read_only           | off               
+D transform_null_equals           | off               
+D unix_socket_directory           | /tmp              
+D unix_socket_group               |                   
+D unix_socket_permissions         | 511               
+D update_process_title            | on                
+D vacuum_cost_delay               | 0                 
+D vacuum_cost_limit               | 200               
+D vacuum_cost_page_dirty          | 20                
+D vacuum_cost_page_hit            | 1                 
+D vacuum_cost_page_miss           | 10                
+D vacuum_defer_cleanup_age        | 0                 
+D vacuum_freeze_min_age           | 100000000         
+D vacuum_freeze_table_age         | 150000000         
+D wal_block_size                  | 8192              
+D wal_buffers                     | 8   
+D wal_debug                       | off               
+D wal_keep_segments               | 0              
+A wal_level                       | minimal  
+D wal_receiver_status_interval    | 10            
+D wal_segment_size                | 2048              
+D wal_sender_delay                | 200              
+A wal_sync_method                 | fdatasync         
+D wal_writer_delay                | 200               
+A work_mem                        |                   
+D xmlbinary                       | base64            
+D xmloption                       | content           
+D zero_damaged_pages              | off               
+};
+
+    my %pgs;
+    for my $line (split /\n/ => $pg_settings) {
+        next unless $line =~ /^([ASD]) (\w+)\s+\|\s*(.+?)\s*$/o;
+        my ($type,$name,$val) = ($1,$2,$3);
+        $pgs{$name} = {type => $type, value => $val};
+    }
+
+    print qq{<tr><th>Cluster</th><th>Config</th><th>Value</th><th>Source</th></tr>\n};
+    for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $info = $data{postgres}{active_port}{$cluster};
+        my $port = $info->{port} || '';
+        next if ! defined $info->{setting}{data_directory};
+        next if exists $opt{skippgport} and $opt{skippgport} =~ /\b$port\b/;
+        my $socketdir = $info->{socketdir};
+        my $set = $info->{setting};
+        my @var;
+        for my $var (sort { lc($a) cmp lc($b) } keys %pgs) {
+            my $type = $pgs{$var}->{type};
+            next if $type eq 'S';
+            next if ! exists $set->{$var};
+            my $source = $set->{$var}{source};
+            my $unit = $set->{$var}{unit} || '';
+            $source = 'Conf' if $source eq 'configuration file';
+            $source = '<b>User</b>' if $source eq 'user';
+            $source = 'Set' if $source eq 'override';
+            $source = 'Def' if $source eq 'default';
+
+            my $value = $set->{$var}{value};
+            my $orig = $pgs{$var}->{value};
+            $orig =~ s/\s+$//;
+            next if $source eq 'Def' and ($var =~ /autovac/ or $var eq 'archive_command' or $var =~ /bgwriter/);
+            next if $source eq 'Def' and $var eq 'unix_socket_directory' and $value !~ /\w/;
+            next if $source eq 'Def' and $value eq 'unset'; ## old style
+            if ($set->{$var}{source} ne 'default') {
+                my $def = $pgs{$var}->{value};
+                $def =~ s/\n/ /g;
+                $def = '(empty string)' if $def =~ /^\s*$/;
+                if ($use_balloons and $format eq 'wiki') {
+                    $source = "<balloon title='Default is: $def'>$source</balloon>";
+                }
+            }
+            if ($type eq 'A' or $value ne $orig) {
+                push @var, [$var,$value,$source,$unit];
+            }
+        }
+        my $number = @var;
+        my $firstrow = 1;
+        print qq{<tr><th rowspan="$number">$port<br />$socketdir</th>};
+        for my $var (@var) {
+            printf qq{%s<td>$var->[0]</td><td><b>%s</b></td><td>$var->[2]</td></tr>\n},
+                $firstrow ? '' : '<tr>',
+                pretty_conf($var->[0],$var->[1],$var->[3]);
+            $firstrow and $firstrow = 0;
+        }
+    }
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_postgres_config
+
+
+sub html_postgres_recovery {
+
+    ## Show information for warm-standby clusters (in recovery mode)
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{active_port};
+
+    ## Make sure we have at least one in recovery mode
+    my $found = 0;
+    for my $cluster (keys %{$data{postgres}{active_port}}) {
+        if (exists $data{postgres}{active_port}{$cluster}{recfile}) {
+            $found = 1;
+            last;
+        }
+    }
+    return if !$found;
+
+    print qq{<tr><th$vtop>${wrap}&rarr; Postgres recovery.conf:</th><td><br /><table class="plain">\n};
+
+    print q{<tr><th>Cluster</th><th>recovery.conf</th></tr>};
+    for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $c = $data{postgres}{active_port}{$cluster};
+        next if ! exists $c->{recfile};
+        my $port = $c->{port} || '';
+        next if exists $opt{skippgport} and $opt{skippgport} =~ /\b$port\b/;
+        my $socketdir = $c->{socketdir};
+        my $recfile = $c->{recfile};
+        $recfile =~ s/ +/ /g;
+        $recfile =~ s{\n}{<br />\n};
+        print qq{<tr><th>$port<br />$socketdir</th><td>$recfile</td></tr>\n};
+    }
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_postgres_recovery
+
+
+sub html_postgres_problems {
+
+    ## Show any known problems that don't fit elsewhere
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{problems};
+
+    print qq{<tr><th$vtop>${wrap}&rarr; Postgres potential problems:</th><td><br /><table class="plain">\n};
+
+    print q{<tr><th>Cluster/<br />Database</th><th>Problem</th></tr>};
+    for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $c = $data{postgres}{active_port}{$cluster};
+        next if ! exists $c->{problems};
+        my $port = $c->{port} || '';
+        next if exists $opt{skippgport} and $opt{skippgport} =~ /\b$port\b/;
+        my $socketdir = $c->{socketdir};
+        for my $db (keys %{$c->{db}}) {
+            my $info = $c->{db}{$db};
+            next if ! exists $info->{problems};
+            my $problems = '';
+            if ($info->{disabled_triggers}) {
+                $problems .= "<b>DISABLED TRIGGERS:</b><ul>\n";
+                for my $t (@{$info->{disabled_triggers}}) {
+                    my ($table,$trigger,$type) = @$t;
+                    $problems .= "<li>$trigger on table $table ($type)</li>\n";
+                }
+                $problems .= "</ul>\n";
+            }
+            print qq{<tr><th>$port<br />$socketdir<br />$db</th><td>$problems</td></tr>\n};
+        }
+    }
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_postgres_recovery
+
+
+sub html_postgres_pgconfig {
+
+    ## Show the output of pg_config
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{pgconfig};
+
+    print qq{<tr><th$vtop>${wrap}&rarr; pg_config info</th><td><br /><table class="plain">\n};
+    for my $row (sort keys %{$data{postgres}{pgconfig}}) {
+        print qq{<tr><th>$row</th>\n};
+        print qq{<td>$data{postgres}{pgconfig}{$row}</td>\n};
+        print qq{</tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_postgres_pgconfig
+
+
+sub html_postgres_databases {
+
+    ## Show active cluster database information
+
+    return if ! exists $data{postgres} or ! exists $data{postgres}{active_port};
+
+    C: for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $cinfo = $data{postgres}{active_port}{$cluster};
+        my $port = $cinfo->{port} || '';
+        next if exists $opt{skippgport} and $opt{skippgport} =~ /\b$port\b/;
+        next if exists $cinfo->{startingup};
+        next if ! exists $cinfo->{setting}{data_directory};
+        my $socketdir = $cinfo->{socketdir};
+        print qq{<tr><th$vtop>${wrap}&rarr; PG DBs<br />$port<br />$socketdir</th><td><br /><table class="plain">\n};
+
+        my ($usets,$useds,$usesl,$usebc) = (0,0,0,0);
+        for my $db (keys %{$cinfo->{db}}) {
+            my $dinfo = $cinfo->{db}{$db};
+            next if $db eq 'template1';
+            if (defined $dinfo->{tablespace} and length $dinfo->{tablespace} and $dinfo->{tablespace} ne 'pg_default') {
+                $usets = 1;
+            }
+            if (defined $dinfo->{description} and length $dinfo->{description}) {
+                $useds = 1;
+            }
+            if (exists $dinfo->{slony}) {
+                $usesl = 1;
+            }
+        }
+
+        print qq{<tr><th>Name</th>\n};
+        print q{<th>Encoding/<br />Owner</th>};
+        print q{<th>Template/<br />Connectable/<br />Age</th>};
+        print q{<th>Size</th>};
+        print q{<th>Schemas/<br />Tables/<br />Functions</th>};
+        print q{<th>Languages</th>};
+        $cinfo->{hascontrib} and print q{<th>Contrib</th>};
+        $usesl and print q{<th>Slony</th>};
+        $usets and print q{<th>Tablespace</th>};
+        $useds and print q{<th>Description</th>};
+        print qq{</tr>\n};
+
+        for my $db (sort keys %{$data{postgres}{active_port}{$cluster}{db}}) {
+            my $dinfo = $data{postgres}{active_port}{$cluster}{db}{$db};
+            my $enc = qq{<b>$dinfo->{encoding}</b>};
+            my $owner = $dinfo->{owner};
+            my $istemplate = $dinfo->{template} eq 'Yes' ? '<b>Yes</b>' : 'No';
+            my $canconn = $dinfo->{canconn} eq 'No' ? '<b>No</b>' : 'Yes';
+            my $age = $dinfo->{age};
+            my $size = qq{<b>$dinfo->{size}</b>};
+            my $schemas = $dinfo->{relcount}{'schema'} || 0;
+            my $tables = $dinfo->{relcount}{'r'} || 0;
+            my $indexes = $dinfo->{relcount}{'i'} || 0;
+            my $views = $dinfo->{relcount}{'v'} || 0;
+            my $sequences = $dinfo->{relcount}{'S'} || 0;
+            my $funcs = $dinfo->{relcount}{'f'} || 0;
+            if ($dinfo->{canconn} eq 'No') {
+                $schemas = $tables = $indexes = $views = $sequences = $funcs = $UNKNOWN_VALUE;
+            }
+
+            my $stf = "$schemas<br />$tables<br />$funcs";
+            if ($db eq 'template0' and $dinfo->{canconn} eq 'No') {
+                $stf = 'N/A';
+            }
+
+            my $languages = join '<br />' => sort keys %{$dinfo->{language}};
+            $languages ||= '&nbsp;';
+
+            my $tablespace = $dinfo->{tablespace};
+            my $description = $dinfo->{tablespace};
+            print qq{<tr><td><b>$db</b></td><td>$enc<br />$owner</td>};
+            print qq{<td>$istemplate<br />$canconn<br />$age</td><td>$size</td>};
+            print qq{<td>$stf</td>};
+            print qq{<td><b>$languages</b></td>};
+            if ($cinfo->{hascontrib}) {
+                my $contribs = '&nbsp;';
+                if (exists $dinfo->{contrib}) {
+                    $contribs = '<ul>';
+                    for my $cname (sort keys %{$dinfo->{contrib}}) {
+                        $contribs .= "<li>$cname</li>";
+                    }
+                    $contribs .= '</ul>';
+                }
+                print qq{<td>$contribs</td>};
+            }
+            printf qq{%s%s%s</tr>\n},
+            $usets ? qq{<td>$dinfo->{tablespace}</td>} : '',
+            $useds ? qq{<td>$dinfo->{description}</td>} : '',
+            $usesl ? (sprintf q{<td>%s</td>}, exists $dinfo->{slony} ? 'Yes' : 'No') : '',
+        }
+        print "</table></td></tr>\n\n";
+    }
+
+    return;
+
+} ## end of html_postgres_databases
+
+
+sub html_postgres_tablespaces {
+
+    ## Show any non-standard tablespaces
+
+    return if ! exists $data{gottablespaces};
+
+    C: for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $info = $data{postgres}{active_port}{$cluster};
+        next if ! exists $info->{dbtablespace};
+        my $port = $info->{port} || '';
+        my $socketdir = $info->{socketdir};
+
+        print qq{<tr><th$vtop>${wrap}&rarr; Tablespace info<br />$port<br />$socketdir</th><td><br /><table class="plain">\n};
+        print qq{<tr><th>Name</th><th>Path</th></tr>\n};
+        for my $name (sort keys %{$info->{dbtablespace}}) {
+            my $path = $info->{dbtablespace}{$name}[0];
+            print qq{<tr><td>$name</td><td><b>$path</b></td></tr>\n};
+        }
+        print "</table></td></tr>\n\n";
+    }
+
+    return;
+
+} ## end of html_postgres_tablespaces
+
+
+sub html_postgres_slony {
+
+    ## Show Slony information for each cluster and database that uses it
+
+    return if ! exists $data{gotslony};
+
+    C: for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $info = $data{postgres}{active_port}{$cluster};
+        next if ! exists $info->{gotslony};
+        my $port = $info->{port} || '';
+        my $socketdir = $info->{socketdir};
+
+        for my $db (sort keys %{$data{postgres}{active_port}{$cluster}{db}}) {
+            my $info = $data{postgres}{active_port}{$cluster}{db}{$db};
+            next if ! exists $info->{slony};
+            print qq{<tr><th$vtop>${wrap}&rarr; Slony info<br />$port<br />$socketdir<br />$db</th><td><br /><table class="plain">\n};
+            my $paths = join '<br />' => @{$info->{slony}{paths}};
+            my $tables = 'NONE!';
+            if (defined $info->{slony}{tables}) {
+                $tables = join '<br />' => @{$info->{slony}{tables}};
+            }
+            my $seqs = 'NONE!';
+            if (defined $info->{slony}{sequences}) {
+                $seqs = exists $info->{slony}{sequences}
+                    ? (join '<br />' => @{$info->{slony}{sequences}})
+                    : 'none';
+            }
+            print qq{<tr><td>Schema:</td><td><b>$info->{slony}{schema}</b></td></tr>\n};
+            print qq{<tr><td>Paths:</td><td><b>$paths</b></td></tr>\n};
+            print qq{<tr><td>Tables:</td><td><b>$tables</b></td></tr>\n};
+            print qq{<tr><td>Sequences:</td><td><b>$seqs</b></td></tr>\n};
+            print "</table></td></tr>\n\n";
+        }
+    }
+
+    return;
+
+} ## end of html_postgres_slony
+
+
+sub html_postgres_bucardo {
+
+    ## Show Bucardo information
+
+    return if ! exists $data{gotbucardo};
+
+    C: for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $info = $data{postgres}{active_port}{$cluster};
+        next if ! exists $info->{gotbucardo};
+        my $port = $info->{port} || '';
+        my $socketdir = $info->{socketdir};
+
+        for my $db (sort keys %{$data{postgres}{active_port}{$cluster}{db}}) {
+            my $info = $data{postgres}{active_port}{$cluster}{db}{$db};
+            next if ! exists $info->{bucardo};
+            print qq{<tr><th$vtop>${wrap}&rarr; Bucardo info<br />$port<br />$socketdir<br />$db</th><td><br /><table class="plain">\n};
+
+            my $bc = $info->{bucardo};
+
+            my $dbs = '';
+            if (keys %{$bc->{db}}) {
+                $dbs = '';
+                for my $name (sort keys %{$bc->{db}}) {
+                    my $i = $bc->{db}{$name};
+                    $dbs .= sprintf '%s: %s %s dbname=%s',
+                        $name,
+                        ($i->{dbhost} ? "dbhost=$i->{dbhost}" : ''),
+                        ($i->{dbport}==5432 ? '' : "dbport=$i->{dbport}"),
+                        $i->{dbname};
+                    $i->{sourcelimit} and $dbs .= " sourcelimit=$i->{sourcelimit}";
+                    $i->{targetlimit} and $dbs .= " targetlimit=$i->{targetlimit}";
+                    if (exists $i->{server_side_prepares} and $i->{server_side_prepares} ne 't') {
+                        $dbs .= ' ssp=FALSE';
+                    }
+                    if (exists $i->{makedelta} and $i->{makedelta} eq 'on') {
+                        $dbs .= ' makedelta=ON';
+                    }
+                    $dbs .= '<br />';
+                }
+            }
+            $dbs ||= 'NONE';
+
+            ## Only the intereting ones in the future
+            my $configs = 'NONE?!';
+            if (keys %{$bc->{bucardo_config}}) {
+                $configs = join '<br />', map { "$_: $bc->{bucardo_config}{$_}{value}" }
+                    sort grep { defined $bc->{bucardo_config}{$_}{value} } keys %{$bc->{bucardo_config}};
+            }
+
+            my $tables = '';
+            if (keys %{$bc->{goat}}) {
+                $tables = join '<br />',
+                    sort
+                    map { "$db $bc->{goat}{$_}{schemaname}.$bc->{goat}{$_}{tablename}" }
+                    grep { $bc->{goat}{$_}{reltype} eq 'table' }
+                    keys %{$bc->{goat}};
+            }
+            $tables ||= 'NONE';
+
+            my $sequences = '';
+            if (keys %{$bc->{goat}}) {
+                $sequences = join '<br />',
+                    sort
+                    map { "$bc->{goat}{$_}{schemaname}.$bc->{goat}{$_}{tablename}" }
+                    grep { $bc->{goat}{$_}{reltype} eq 'sequence' }
+                    keys %{$bc->{goat}};
+            }
+            $sequences ||= 'NONE';
+
+            my $syncs = '';
+            if (keys %{$bc->{sync}}) {
+                for my $name (sort keys %{$bc->{sync}}) {
+                    my $s = $bc->{sync}{$name};
+                    my $target = $s->{targetdb} ? "DB $s->{targetdb}"
+                        : "DBGROUP $s->{targetgroup}";
+                    $syncs .= "$name $s->{synctype} $s->{source} => $target";
+                    for my $zero (qw/limitdbs onetimecopy maxkicks rebuild_index/) {
+                        $s->{$zero} and $syncs .= " $zero=$s->{$zero}";
+                    }
+                    $s->{checktime} and $syncs .= " checktime=$s->{checktime}";
+                    $syncs .= '<br />';
+                }
+            }
+            $syncs ||= 'NONE';
+
+            print qq{<tr><td>Config:</td><td><b>$configs</b></td></tr>\n};
+            print qq{<tr><td>Databases:</td><td><b>$dbs</b></td></tr>\n};
+            print qq{<tr><td>Tables:</td><td><b>$tables</b></td></tr>\n};
+            print qq{<tr><td>Sequences:</td><td><b>$sequences</b></td></tr>\n};
+            print qq{<tr><td>Syncs:</td><td><b>$syncs</b></td></tr>\n};
+            print "</table></td></tr>\n\n";
+        }
+    }
+
+    return;
+
+} ## end of html_postgres_bucardo
+
+
+sub html_postgres_postgis {
+
+    ## Show some simple Postgis information for each cluster and database that uses it
+
+    return if ! exists $data{gotpostgis};
+
+    C: for my $cluster (sort { $a cmp $b } keys %{$data{postgres}{active_port}}) {
+        my $info = $data{postgres}{active_port}{$cluster};
+        next if ! exists $info->{gotpostgis};
+        my $port = $info->{port} || '';
+        my $socketdir = $info->{socketdir};
+
+        for my $db (sort keys %{$data{postgres}{active_port}{$cluster}{db}}) {
+            my $info = $data{postgres}{active_port}{$cluster}{db}{$db};
+            next if ! exists $info->{postgis} or ! exists $info->{postgis}{postgis_full_version};
+            print qq{<tr><th$vtop>${wrap}&rarr; PostGIS<br />$port<br />$socketdir<br />$db</th><td><br /><table class="plain">\n};
+            print qq{<tr><td>Version:</td><td><b>$info->{postgis}{postgis_full_version}</b></td></tr>\n};
+            print "</table></td></tr>\n\n";
+        }
+    }
+
+    return;
+
+} ## end of html_postgres_postgis
+
+
+sub html_mysql {
+
+    ## Show MySQL information
+
+    return if ! exists $data{mysql};
+
+    print qq{<tr><th$vtop>MySQL:</th><td><br /><table class="plain">\n};
+
+    print qq{<tr><td>Version:</td><td><b>$data{version}{mysql}</b></td></tr>\n};
+    print qq{<tr><td>Port:</td><td><b>$data{mysql}{port}</b></td></tr>\n};
+    print qq{<tr><td>Socket:</td><td><b>$data{mysql}{socket}</b></td></tr>\n};
+    if ($data{mysql}{status}) {
+        my $stat = join '<br />' => @{$data{mysql}{status}};
+        print qq{<tr><td>Status:</td><td>$stat</td></tr>\n};
+    }
+
+    my $dbs = join '<br />' => map { "<b>$_</b> ($data{mysql}{database}{$_}{tables} tables)" }
+        sort keys %{$data{mysql}{database}};
+
+    print qq{<tr><td>Databases:</td><td>$dbs</td></tr>\n};
+
+    print qq{</table></td></tr>\n\n};
+
+    return;
+
+} ## end of html_mysql
+
+
+sub html_ulimits {
+
+    return if ! exists $data{ulimit};
+
+    print qq{<tr><th$vtop>${wrap}User limits:</th><td><br /><table class="plain">\n};
+    print q{<tr><th>Name</th><th>Limit</th></tr>};
+    for my $name (sort keys %{$data{ulimit}}) {
+        my $limit = $data{ulimit}{$name};
+        print qq{<tr><td>$name</td><td><b>$limit</b></td></tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_ulimits
+
+
+sub html_envs {
+
+    return if ! exists $data{ENV};
+
+    print qq{<tr><th$vtop>${wrap}ENVs:</th><td><br /><table class="plain">\n};
+    print qq{<tr><th>Name</th><th>Value</th></tr>\n};
+
+    my $skipnames = join '|' =>
+        qw/COLORTERM DISPLAY G_BROKEN_FILENAMES LS_COLORS OLDPWD PGUSER PWD SHLVL STY TERM TERMCAP XAUTHORITY GCC_SPECS PS1
+          MAIL USER LOGNAME HOME HIST.* PATH/;
+    my $skipre = qr{\b(?:$skipnames)\b};
+
+    for my $e (sort { $a cmp $b } keys %{$data{ENV}}) {
+        next if $e =~ $skipre;
+        next if $e =~ /^SSH_/;
+        my $val = $data{ENV}{$e} || '&nbsp;';
+        if ($e =~ /PATH$/ or $e =~ /_DIRS$/ or $e eq 'TEXINPUTS' or $e eq 'INFODIR') {
+            $val =~ s/^://;
+            $val =~ s{:}{:<br />}g;
+        }
+        if ($e =~ /^CONFIG_PROTECT/) {
+            $val =~ s{ }{<br />}g;
+        }
+        print qq{<tr><td>$e</td><td>$val</td></tr>\n};
+    }
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_envs
+
+
+sub html_yum {
+
+    return if ! exists $data{yum};
+
+    print qq{<tr><th$vtop>${wrap}yum installs:</th><td><br /><table class="plain">\n};
+    print qq{<tr><th>Name</th><th>Version</th>\n};
+    print qq{<th>Name</th><th>Version</th></tr>\n};
+
+    my $set = 1;
+    for my $name (sort { $a cmp $b } keys %{$data{yum}{installed}}) {
+        my $ver = $data{yum}{installed}{$name};
+        if (1==$set) {
+            print qq{<tr><td>$name</td><td>$ver</td>\n};
+            $set = 2;
+        }
+        else {
+            print qq{<td>$name</td><td>$ver</td></tr>\n};
+            $set = 1;
+        }
+    }
+    2==$set and print '<td>&nbsp;</td></tr>';
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_yum
+
+
+sub html_aptitude {
+
+    return if ! exists $data{aptitude};
+
+    print qq{<tr><th$vtop>${wrap}apt-get installs:</th><td><br /><table class="plain">\n};
+    print qq{<tr><th>Name</th><th>Automatic?</th>\n};
+    print qq{<th>Name</th><th>Automatic</th></tr>\n};
+
+    my $set = 1;
+    for my $name (sort { $a cmp $b } keys %{$data{aptitude}{installed}}) {
+        my $auto = $data{aptitude}{installed}{$name} ? 'Yes' : 'No';
+        if (1==$set) {
+            print qq{<tr><td>$name</td><td>$auto</td>\n};
+            $set = 2;
+        }
+        else {
+            print qq{<td>$name</td><td>$auto</td></tr>\n};
+            $set = 1;
+        }
+    }
+    2==$set and print '<td>&nbsp;</td></tr>';
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_aptitude
+
+
+sub html_gems {
+
+    return if ! exists $data{gems};
+
+    print qq{<tr><th$vtop>${wrap}Ruby local gems:</th><td><br /><table class="plain">\n};
+
+    my $table = make_table(
+        {
+            header => ['Gem','Version'],
+            data   => $data{gems},
+            onecol => 10,
+        });
+
+    print $table;
+
+    print "</table></td></tr>\n\n";
+
+    return;
+
+} ## end of html_gems
+
+
+sub escape_html {
+
+  my $string = shift;
+
+  $string =~ s{<}{&lt;}g;
+  $string =~ s{>}{&gt;}g;
+  $string =~ s{\n}{<br />}g;
+
+  return $string;
+
+} ## end of escape_html
+
+
+sub make_table {
+
+    my $arg = shift;
+
+    my $header = $arg->{header} or die;
+    my $data = $arg->{data} or die;
+    my $numitems = keys %$data;
+
+    ## How many items before we go to multiple columns?
+    my $onecol = $arg->{onecol} || 15;
+
+    ## How many columns?
+    my $cols = $numitems <= $onecol ? 1
+        : $numitems <= $onecol*2 ? 2
+        : 3;
+
+    my $table = q{<tr>};
+    for (1..$cols) {
+        $table .= join '' => map { "<th>$_</th>" } @$header;
+    }
+    $table .= qq{</tr>\n};
+
+    my $pos = 0;
+    for my $name (sort keys %$data) {
+        $pos++;
+        my $val = $data->{$name};
+        $table .= sprintf q{%s<td>%s</td><td>%s</td>%s},
+            1==$pos ? '<tr>' : '',
+            $name,
+            $val,
+            $pos==$cols ? "</tr>\n" : '';
+        if ($pos >= $cols) {
+            $pos = 0;
+        }
+    }
+
+    if ($pos and $pos < $cols) {
+        for ($pos .. $cols-1) {
+            $table .= '<td> &nbsp; </td>';
+        }
+        $table .= "</tr>\n";
+    }
+
+    return $table;
+
+} ## end of make_table
+
index 987924d2d3c457eda4b323cb55c01333e64c4f5d..9924569cfceb9f3de68d9fcd1e8093e2ed4c3bfc 100644 (file)
@@ -1,9 +1,9 @@
 Summary:       Gather information about a particular computer
 Name:          boxinfo
 Version:       1.4.0
-Release:       2PGDG%{?dist}
+Release:       3PGDG%{?dist}
 License:       BSD
-Source0:       https://bucardo.org/downloads/%{name}.pl
+Source0:       %{name}.pl
 Source2:       README.%{name}
 URL:           https://bucardo.org/wiki/Boxinfo
 BuildArch:     noarch
@@ -17,11 +17,10 @@ highly developed Postgres section. It was developed at End Point
 Corporation by Greg Sabino Mullane.
 
 %prep
-%if 0%{?suse_version} >= 1315
-%{__cp} -p %{SOURCE0} .
-%endif
+echo no prep stage needed
 
 %build
+echo no build stage needed
 
 %install
 %{__rm} -rf %{buildroot}
@@ -29,7 +28,7 @@ Corporation by Greg Sabino Mullane.
 %{__install} -d -m 755 %{buildroot}%{_bindir}
 %{__install} -d -m 755 %{buildroot}%{_docdir}/%{name}
 
-%{__install} -m 755 %{name}.pl %{buildroot}%{_bindir}/%{name}.pl
+%{__install} -m 755 %{SOURCE0} %{buildroot}%{_bindir}/%{name}.pl
 %{__install} -m 644 %{SOURCE2} %{buildroot}%{_docdir}/%{name}/
 
 %files
@@ -38,6 +37,9 @@ Corporation by Greg Sabino Mullane.
 %attr(644,root,root) %{_docdir}/%{name}/README.%{name}
 
 %changelog
+* Mon Apr 7 2025 Devrim Gündüz <devrim@gunduz.org> - 1.4.0-3PGDG
+- Fix instalation issue on recent platforms
+
 * Fri Feb 16 2024 Devrim Gündüz <devrim@gunduz.org> - 1.4.0-2PGDG
 - Modernise the spec file.
 - Add PGDG branding.