#!/usr/bin/env perl #****************************************************************************** # @(#) pvs.pl #****************************************************************************** # @(#) Copyright (C) 2016 by KUDOS BVBA . All rights reserved. # # This program is a free software; you can redistribute it and/or modify # it under the same terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details #****************************************************************************** # This script will display HP-UX LVM information in Linux style # # Based on https://jreypo.wordpress.com/2010/02/16/linux-lvm-commands-in-hp-ux/ # # @(#) HISTORY: see perldoc 'pvs.pl' # ----------------------------------------------------------------------------- # DO NOT CHANGE THIS FILE UNLESS YOU KNOW WHAT YOU ARE DOING! #****************************************************************************** #****************************************************************************** # PRAGMA's & LIBRARIES #****************************************************************************** use strict; use warnings; use POSIX qw(uname ceil); use Getopt::Long; use Pod::Usage; use Data::Dumper; $|++; #****************************************************************************** # DATA structures #****************************************************************************** my ($os, $version, $footer, $warning); my $warn_devices = 500; my %options; my (@pvol, @cvol, @vgdisplay); my %cdsf; #****************************************************************************** # SUBroutines #****************************************************************************** sub parse_pvols { my @pvol = @_; my $pv_command; my %pvdisplay; my (@dsf,@sorted_dsf,@has_underscore); unless (@pvol) { print "-- no disks found --\n"; return; } # collect DSF/cDSF & pvol info foreach my $pvol (@pvol) { my (@dsf_data, @cdsf_data, @other_dsf); my ($dsf, $cdsf, $is_cdsf) = ("","",0); chomp ($pvol); # check for DSF or cDSF? if ($pvol =~ m/cdisk/) { $pvol =~ s#cdisk/##; $is_cdsf = 1; # we have the cDSF, get the DSF (should only be 1!) @dsf_data = grep (/rcdisk\/${pvol}:/, @cvol); ($dsf) = (split (/:/, $dsf_data[0]))[1]; chomp ($dsf); $dsf =~ s#/dev/rdisk/## if (defined ($dsf)); if ($dsf eq "") { # workaround for bug in ioscan -FkN -m cluster_dsf but it works without '-F' @other_dsf = `/usr/sbin/ioscan -kn -m cluster_dsf /dev/cdisk/$pvol 2>/dev/null | grep "$pvol" | awk '{print \$2}' | cut -f4 -d'/'`; $dsf = $other_dsf[0]; chomp ($dsf); if ($dsf eq "") { print STDERR "ERROR: unable to find dsf for $pvol\n"; next; } else { # save the cDSF push (@{$cdsf{$dsf}}, $pvol); } } else { # save the cDSF push (@{$cdsf{$dsf}}, $pvol); } } else { # we have the DSF, get the cDSF (could be >1) @cdsf_data = grep (/rdisk\/${pvol}:/, @cvol); ($dsf = $pvol) =~ s#disk/## if (defined ($dsf)); # set cDSF flag if we found cDSFs $is_cdsf = 1 if (@cdsf_data); # loop over cDSF data foreach my $cdsf_data (@cdsf_data) { ($cdsf) = (split (/:/, $cdsf_data))[0]; $cdsf =~ s#/dev/rcdisk/##; # save the cDSF push (@{$cdsf{$dsf}}, $cdsf) if (defined ($cdsf)); } } # pvdisplay on DSF/cDSF (must be correct one!) but record it under $dsf # no error handling here, error indicates: inactive/non-LVM stuff if ($is_cdsf) { # get info of the first cDSF @{$pvdisplay{$dsf}} = `/usr/sbin/pvdisplay -F /dev/cdisk/@{$cdsf{$dsf}}[0] 2>/dev/null`; } else { @{$pvdisplay{$dsf}} = `/usr/sbin/pvdisplay -F /dev/disk/${dsf} 2>/dev/null`; } push (@dsf, $dsf); } # set comparison operator based on dsf type @has_underscore = grep { /_/ } @dsf; if (@has_underscore) { @sorted_dsf = sort { substr($a, 4) cmp substr($b, 4) } (@dsf); } else { @sorted_dsf = sort { substr($a, 4) <=> substr($b, 4) } (@dsf); } # display pvol data (sorted by their device number: diskYYYY or diskXXXX_pY) foreach my $dsf (@sorted_dsf) { chomp ($dsf); my ($cdsf, $cdsf_bit) = ("",""); # make cDSF bit if (@{$cdsf{$dsf}}) { # display only the first cDSF! (in case of multiples) $cdsf = @{$cdsf{$dsf}}[0]; $cdsf_bit = "(".scalar (@{$cdsf{$dsf}}).")"; } else { $cdsf = "n/a"; } if (@{$pvdisplay{$dsf}}) { # check for PV data foreach my $pv_entry (@{$pvdisplay{$dsf}}) { my ($vg_name, $pv_status)= ("",""); my ($pv_total_pe, $pv_size_pe, $pv_free_pe, $pv_stale_pe) = (0,0,0,0); my ($pv_size, $pv_free) = (0,0); my @pv_data = split (/:/, $pv_entry); # loop over PVOL data foreach my $pv_field (@pv_data) { $vg_name = $1 if ($pv_field =~ m%^vg_name=/dev/(.*)%); $pv_status = $1 if ($pv_field =~ m%^pv_status=(.*)%); $pv_total_pe = $1 if ($pv_field =~ m%^total_pe=(.*)%); $pv_size_pe = $1 if ($pv_field =~ m%^pe_size=(.*)%); $pv_free_pe = $1 if ($pv_field =~ m%^free_pe=(.*)%); $pv_stale_pe = $1 if ($pv_field =~ m%^stale_pe=(.*)%); } # calculate sizes $pv_size = $pv_total_pe * $pv_size_pe; $pv_size /= 1024 unless ($options{'size'} =~ /MB/i); $pv_size = ceil ($pv_size); $pv_free = $pv_free_pe * $pv_size_pe; $pv_free /= 1024 unless ($options{'size'} =~ /MB/i); $pv_free = ceil ($pv_free); # report data printf STDOUT ("%-12s %-10s %-4s %-25s %-20s %-7d %-8d %-8d %-8d\n", $dsf, $cdsf, $cdsf_bit, $vg_name, $pv_status, $pv_size_pe, $pv_size, $pv_free, $pv_stale_pe) } } else { unless ($options{'active'}) { printf STDOUT ("%-12s %-10s %-4s %-25s %-20s\n", $dsf, $cdsf, $cdsf_bit, "n/a", "not active/not LVM"); } } } } sub print_header { unless ($options{'terse'}) { if (scalar (@pvol) > $warn_devices) { $warning = qq{ WARNING: You have more than $warn_devices disk devices on your system. Collecting the PV information will take some time... }; print STDOUT "$warning"; } printf STDOUT ("\n%-12s %-10s %-4s %-25s %-20s %-7s %-8s %-8s %-8s\n", "PV", "cDSF", "(#)", "VG", "Status", "PE Size", "PV Size", "PV Free", "Stale PE"); } } #****************************************************************************** # MAIN routine #****************************************************************************** # where and what am I? die ("ERROR: must be invoked as root\n") if ($<); ($os, $version) = (uname())[0,2]; die ("ERROR: only runs on HP-UX v11.31") unless ($os eq 'HP-UX' and $version eq 'B.11.31'); # process command-line options if ( @ARGV > 0 ) { Getopt::Long::Configure('prefix_pattern=(--|-|\/)', 'bundling', 'no_ignore_case'); GetOptions( \%options, qw( help|h|? size|s=s vg|g=s active|a terse|t )); } # check options if ($options{'help'}) { pod2usage(-verbose => 3); exit (0); }; unless ($options{'size'}) { $options{'size'} = 'GB'; }; if ($options{'vg'}) { $options{'vg'} =~ s#/dev/##; # force --active off delete $options{'active'}; }; # fetch cDSF map @cvol = `/usr/sbin/ioscan -kFN -m cluster_dsf 2>/dev/null`; die "ERROR: could not retrieve cDSF info" if ($?); # fetch PVOLs (non-boot) if ($options{'vg'}) { # pvols can be cluster-wide or regular disks here! (output: cdisk/diskXXXX or disk/diskYYYY) @pvol = `/usr/sbin/vgdisplay -vF "/dev/${options{'vg'}}" 2>/dev/null | grep "^pv_name" | cut -f1 -d':' | cut -f2 -d'=' | cut -f3-4 -d '/'`; unless (@pvol) { die "ERROR: could not retrieve VG info for ${options{'vg'}}" }; print_header; parse_pvols (@pvol); } else { # output: diskYYYY @pvol = `/usr/sbin/ioscan -kFN -C disk 2>/dev/null | cut -f9,13 -d':' | tr -d ':'`; die "ERROR: could not retrieve ioscan info" if ($?); print_header; parse_pvols (@pvol); # fetch PVOLs (boot); output: diskYYYY unless ($options{'terse'}) { print "\n-- Boot disk(s):\n"; } @pvol = `/usr/sbin/lvlnboot -v 2>/dev/null | grep 'Boot Disk' | awk '{ print \$1 }' | cut -f4 -d '/'`; die "ERROR: could not retrieve boot info" if ($?); parse_pvols (@pvol); } # footer unless ($options{'terse'}) { $footer = qq{ Note 1: 'PE Size' values are expressed in MB Note 2: 'PV Size' & 'PV Free' values are expressed in GB by default (see --help) Note 3: cDSF: only the first cluster-wide device is shown in case of multiples. The number of cDSF is displayed in parentheses following the cDSF name. Note 4: more detailed information can be obtained by running the pvdisplay(1M), vgdisplay(1M), lvdisplay(1M) commands }; print STDOUT $footer; } exit (0); #****************************************************************************** # End of SCRIPT #****************************************************************************** __END__ #****************************************************************************** # POD #****************************************************************************** # ----------------------------------------------------------------------------- =head1 NAME pvs.pl - Show physical volume information in a terse way (Linux style). =head1 SYNOPSIS pvs.pl [-h|--help] [(-g|--vg)=] [(-s|--size)=] [(-a|--active)] [(-t|--terse)] =head1 OPTIONS =over 2 =item -h | --help S< >Show the help page. =item -a | --active S< >Hide non-active and non-LVM disks. Cannot be used in conjunction with --vg. =item -g | --vg S< >Display physical volumes for a specific volume group. Volume group name should be specified without the "/dev/" prefix. =item -s | --size S< >Show physical volume size in MB or GB (default is GB). =item -t | --terse S< >Do not show header and footer information. =head1 NOTE Collecting & displaying the data might take a considerable amount of time depending on the amount of devices present on the system. =head1 AUTHOR (c) KUDOS BVBA - Patrick Van der Veken =head1 history @(#) 2016-04-12: first version [Patrick Van der Veken] @(#) 2016-04-27: small fixes [Patrick Van der Veken] @(#) 2016-04-27: show all PVOLs & option --active added [Patrick Van der Veken] @(#) 2017-12-12: added support for cluster disks, added --terse [Patrick Van der Veken] @(#) 2019-02-08: fix for comparison operator + remove /dev/ prefix for VG [Patrick Van der Veken] @(#) 2020-03-26: use ceil() to round up to more sensible numbers [Patrick Van der Veken] @(#) 2020-04-25: workaround for bug(?) in ioscan + display error when no dsf is found [Patrick Van der Veken] @(#) 2020-04-28: fix for array check [Patrick Van der Veken]