#!/usr/local/bin/perl ############################################################################## # Copyright 2001, Jonathan Tourtellot (Global Networking and Computing) # # Permission to use, modify and freely distribute this script is explicitly # granted, provided this copyright notice is preserved without modifications. ############################################################################## ############################################################################## # $Header: /home/jon/scripts/RCS/jtplex,v 1.20 2001/12/17 21:25:48 jon Exp $ ############################################################################## # Written by Jonathan Tourtellot (Global Networking and Computing), # with some help from Dave Stuit (Global Networking and Computing). # -t option supplied by Doug Hughes (Doug.Hughes@Eng.Auburn.EDU) # ############################################################################## # jtplex - a program which builds a matrix (target[.lun] x controller) of # the plexes that currently exist in a VxVM configuration. This # includes mirrors, snapshots, etc., including plexes in sub-volumes # (which are listed under the name of the parent plex). The output # is written to STDOUT, and can be easily redirected to a file, or # cut-and-paste from the terminal window to a local file, then # imported into Excel as a .csv file, and examined or made # pretty for a presentation (guess what I do with it). The script # can properly handle LUNs, arbitrary levels of sub-volumes, and # has options to include information about the size of each sub-disk. # The sub-disks are printed out in the order as they appear on the # physical disk (i.e. by block number). ############################################################################## use Sys::Hostname; require "getopts.pl"; $vxprint = "/usr/sbin/vxprint"; $vxdisk = "/usr/sbin/vxdisk"; $jtplex_version = '$Revision: 1.20 $'; ################################################################################ # Learn about the physical disks, and build two hashes, %PlexesOn # and %PlexOffset, with the disk devices as the keys. We'll populate # the hash values later; here, we're only defining the keys, and making # lists of the available controllers and targets, in two seperate NULL hashes. # These last two hashes are used to create the x and y axes for the diagram. sub PhysicalDisks { my ($vxdiskline, $sdName, $state); open VXDISK, "$vxdisk list |" or die "Could not run $vxdisk: $!"; while ($vxdiskline = ) { # Looking for "^c0t0d0", or the like. if ($vxdiskline =~ /^c(\d+)t(\d+)d(\d+)/) { $sdName = "c$1t$2d$3"; $PlexesOn{$sdName} = [ "" ]; $PlexOffset{$sdName} = [ "" ]; # This is also an excellent place to make a list # of available controllers and targets, so we # can easily build the x-y labels. $Controllers{$1} = ""; if ($3 != 0) { # This target has luns, so we write it # as target.lun $Targets{"$2.$3"} = ""; } else { # Normal target, no lun. $Targets{"$2"} = ""; } # Check if this disk is under Volume Manager control. # if it's not, the STATUS field will be "error". # We'll mark disks not under VxVM control as # "(unmanaged)", which sounds better than "error". if ($vxdiskline =~ /error$/) { push @{ $PlexesOn{$sdName} }, "(unmanaged)"; } # Same as above, but this picks up failed/removed disks # and makes a note of that in the output. We first # write a NULL to the hashes, then the real info, # so that the hashed list will look the same as the # ones generated elsewhere, just the name of the # plex will be --failed--, --failing--, or --removed--. } elsif ($vxdiskline =~ /^-(?:\s+\S+){3}\s+(\S+\s+)+?was:c(\d+)t(\d+)d(\d+)/) { $sdName = "c$2t$3d$4"; # get rid of the extra space chop ($state = $1); $PlexesOn{$sdName} = [ "" ]; $PlexOffset{$sdName} = [ "" ]; push @{ $PlexesOn{$sdName} }, "--$state--"; push @{ $PlexOffset{$sdName} }, "0"; # Still need the following, just as above. $Controllers{$2} = ""; if ($4 != 0) { # This target has luns, so we write it # as target.lun $Targets{"$3.$4"} = ""; } else { # Normal target, no lun. $Targets{"$3"} = ""; } } } close VXDISK; return 0; } ################################################################################ # Here, we associate each plex to its physical disk, and populate the # %PlexesOn and %PlexOffset hashes with this information. sub VxParse { my ($diskline, $plex, $len, $plexLen, $kstate, $state, $logonly); my ($plexIsSd); $plexIsSd = 0; open VXPRINT, "$vxprint -rht |" or die "Could not run $vxprint: $!"; # We want the "pl" line, to get the plex name, then all its # associated subdisks, so we know where to put the name. # The possible "interesting" entries after 'pl' are: # 'sd' - subdisk of the plex, # 'sN' - subdisk of subplex in subvolume N of plex. # Once I find a plex, I want to associate all subdisks (sd or sN) # to that plex. The 'sv' and 'pN' entries are only interesting in # that they glue together all the subdisks of a plex. # For stripe-mirrors, maybe we want to count sN's as # part of the p2, rather than as part of the pl... need # to think about this a bit. Matching s2's to the pl # is a bit too coarse, but matching to p2 may be useless. # for now, match to the p2. while ($diskline = ) { if ($diskline =~ /^p(?:l|\d+)\s+(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)/) { # I want not only the plex name, but also its state. If # the plex is in an interesting state (anything other # than KSTATE=ENABLED, STATE=ACTIVE), then that will # be noted in the matrix. $plex = $1; $kstate = $2; $state = $3; $logonly = $4; if ($kstate ne "ENABLED") { $plex = "$plex ($kstate)"; } if ($state ne "ACTIVE") { $plex = "$plex ($state)"; } if ($logonly eq "LOGONLY") { $plex = "$plex (LOG)"; } # shift down to the next line, and then go # until you run out of subdisks of either type # (sd or s2). For each subdisk, add the underlying # device to the %PlexesOn hash. We also add the # offset to the %PlexOffset hash, so we can # correctly order the plexes on the disk # when we print the results of this work. } elsif ($diskline =~ /^s(?:d|\d+)\s+(\S+)(?:\s+\S+){2}\s+(\S+)\s+(\S+)\s+\S+\s+(\S+)/) { $devName = $4; if ($plex eq "") { $plex = "*$1*"; $plexIsSd = 1; } if ($opt_s) { # append the length to the plex name. if ($opt_g) { # sd length in GB $len = $3 / 2097152; $plexLen = sprintf "%0.1f %s", $len, "GB"; } else { # sd length in MB $len = int $3/2048; $plexLen = "$len MB"; } push @{ $PlexesOn{$devName} }, "$plex ($plexLen)"; } else { # just the plex name. push @{ $PlexesOn{$devName} }, $plex; } push @{ $PlexOffset{$devName} }, $2; if ($plexIsSd == 1) { # We've set $plex to the name of a sd, # and need to undo this, or we won't # properly name any other sd's in the # disk group - since $plex is now non-NULL. $plex = ""; $plexIsSd = 0; } } elsif ($diskline =~ /^$/) { # When there's an empty line, we have run off the # sd's for a given plex, so we need to reset $plex $plex = ""; } } close VXPRINT; return 0; } ################################################################################ # Print out the target/controller array as a comma-delimited file, ready # for import into the spreadsheet of your choice. sub ArrayPrint { my ($target, $hostname, $ctlr, $notFirst, $countPlex, $maxNum); my ($numTargets, $targetCount, $m, $TandD); $numTargets = keys %Targets; $hostname = hostname(); print "Plex Layout on $hostname as of ".scalar gmtime()." GMT\n" if not ($opt_t); foreach $DeviceName ( keys %PlexesOn ) { # Sort the plexes in the DeviceName by location (PlexOffset) # In the process, we destroy %PlexOffset, and sort %PlexesOn. # I'm sure there's a more clever way of sorting this... &PlexSort; } $count = scalar(keys(%Targets)); #$count = $count > 12 ? 12 : $count; if ($opt_t) { for ($i=0; $i < $count+1; $i++) { print "cb,"; } print "cb.\n"; } print ",,Targets\n,,"; foreach $target (sort { $a <=> $b } keys %Targets ) { print "$target,"; } print "\nCtlrs,"; $notFirst = 0; # Now for the body of the output. First, a space, then a controller, # then, for each target, the plexes. Where there are multiple plexes, # write them out one per line under the target. On each line after the # first for a given controller, don't print the controller number foreach $ctlr ( sort { $a <=> $b } keys %Controllers ) { if ($notFirst != 0) { # The first line has a "Controller" entry, the rest # need a comma instead. print ","; } else { $notFirst = 1; } print "$ctlr,"; # I need to loop as many times as the largest number # of plexes on any target on this controller. Find the # largest number of plexes on any target on this controller. $maxNum = 0; foreach $target (sort { $a <=> $b } keys %Targets ) { # This is nasty because of the decimal notation for # luns (which is needed so things will sort properly) if ($target =~ /(\d+)\.(\d+)/) { # there's a LUN in this target $TandD = "$1d$2"; } else { # there's only a target $TandD = "${target}d0"; } $m = @{ $PlexesOn{"c${ctlr}t$TandD"} } + 0; # Here's a little hack - if there's no plex # on this valid target, label it as (no plex) # $m is 1 if no plexes, because of the NULL in [0] if ($m == 1) { push @{ $PlexesOn{"c${ctlr}t$TandD"} }, "(no plex)"; # We added a "plex", so increment $m. $m = 2; } if ( $m > $maxNum) { $maxNum = $m; } } # If there are no plexes on the whole controller, the # following doesn't work, since $maxNum = 1. Catch that # special case here. ($maxNum is 1, not zero, because of the # NULL inserted by the vxdisk subroutine.) # This should only happen when there are *no* targets for # a controller; empty plexes and unmanaged disks have been # labelled with a "plex". if ($maxNum == 1) { # No plexes, so no need to go on. print "\n"; } else { # Skip over the 0 element, which is the NULL that the # PhysicalDisks subroutine sticks in. # We're now printing out the plexes. for ($countPlex = 1 ; $countPlex < $maxNum ; $countPlex++) { $targetCount = 1; foreach $target (sort { $a <=> $b } keys %Targets ) { if ($target =~ /(\d+)\.(\d+)/) { # there's a LUN in this target $TandD = "$1d$2"; } else { # there's only a target $TandD = "${target}d0"; } print @{ $PlexesOn{"c${ctlr}t$TandD"} }[$countPlex]; # Print a comma between targets, or a \n at # the end of the line. if ($targetCount == $numTargets) { print "\n"; $targetCount = 1; } else { print ","; $targetCount++; } } if ($countPlex != 0 && $countPlex != $maxNum-1) { print ",,"; } } } } return 0; } ################################################################################ # This function does the sorting so plexes are in the right order on the disk # in the output. sub PlexSort { my (%tempHashOfPlexes, $Offset, $key); # Empty out the old arrays. (Maybe just increment through %PlexOffset?) while (@{ $PlexOffset{$DeviceName} } > 0) { $Offset = pop @{ $PlexOffset{$DeviceName} }; $tempHashOfPlexes{$Offset} = pop @{ $PlexesOn{$DeviceName} }; if ($opt_o) { # Prepend the offset to the name of the plex. $tempHashOfPlexes{$Offset} = "$Offset: $tempHashOfPlexes{$Offset}"; } } # Now we have associated the plex name to the offset on the disk, # so we can sort them back into the original hash, in order of # increasing offset. foreach $key ( sort { $a <=> $b; } keys %tempHashOfPlexes ) { push @{ $PlexesOn{$DeviceName} }, $tempHashOfPlexes{$key}; } return 0; } ################################################################################ # The help subroutine prints out a help message. sub help { print <