#!/usr/bin/perl -w # # groking from: # http://www.backupcentral.com/cgi-bin/netbackup-fom?_highlightWords=bpdbjobs&file=194 # # output's gr; information you actually need from bpbdjobs, and in # a format that fits in a sensibly-sized terminal # # take it use, don't blame me if it breaks something use strict; use POSIX; use Data::Dumper; use Getopt::Long; my $debug = 0; my $sudo = ''; $sudo = "sudo " if ($> != 0); my @job_info_labels = ('jobid', 'jobtype', 'state', 'status', 'class', 'sched', 'client', 'server', 'start', 'elapsed', 'end', 'stunit', 'try', 'operation', 'kbytes', 'files', 'path_last_written', 'percent', 'jobpid', 'owner', 'subtype', 'classtype', 'schedtype', 'priority', 'group', 'master_server', 'retention_units', 'retention_period', 'compression', 'kbyteslastwritten', 'fileslastwritten', 'filelistcount'); my @job_try_labels_1 = ('trypid', 'trystunit', 'tryserver', 'trystarted', 'tryelapsed', 'tryended', 'trystatus', 'trystatusdescription', 'trystatuscount'); my @job_try_labels_2 = ('trybyteswritten','tryfileswritten'); my @vault_info_labels = ('unknown1', 'unknown2', 'unknown3', 'vltrobot', 'vltname', 'vltprofile', 'vltsid', 'vltejects', 'unknown4', 'unknown5', 'unknown6', 'unknown7'); # Jacked from the (TOTALLY UNDOCUMENTED SO I CAN'T USE IT WITHOUT # LOTS OF PAIN) NBU::Job. my %op_codes = ( -1 => ' - ', 25 => 'wai', 2 => 'con', 26 => 'con', 0 => 'act', 1 => 'pos', 6 => 'inv', 7 => 'cat', 8 => 'e/r', 17 => 'lab', # or "u/i"? 27 => 'mnt', 29 => 'pos', 3 => 'wri', 35 => 'wri', 4 => 'inv', 5 => 'dup', ); # The rest are just from bpdbjobs(1M) (operation isn't discussed # there, jerks). my %jt_codes = ( 0 => 'b', 1 => 'a', 2 => 'r', 3 => 'V', 4 => 'd', 5 => 'i', 6 => 'c', 7 => 'v', 8 => 'l', 9 => 'e', 17 => 'C', ); # There's a pretty way to do this, but I'm lazy: my %jt_codes_reverse = ( 'b' => 0, 'a' => 1, 'r' => 2, 'V' => 3, 'd' => 4, 'i' => 5, 'c' => 6, 'v' => 7, 'l' => 8, 'e' => 9, 'C' => 17, ); my %state_codes = ( 0 => 'que', 1 => 'act', 2 => 'req', 3 => 'don', ); sub parse_line { chomp; # Yeah, they really include escaped ,s; ditch them # ... only but there are some legit \\, situations on Windows $_ =~ s#\\\\#//#g; # You can also specify multiple STUs for vaults through different # duplication rules, and there are separated by /, / in the -all_columns # output. (Could I make this stuff up? No.) #$_ =~ s/, /; /g; # Oh yeah, and then as of 6.5, there are some filed that are # non-empty but include only a space... $_ =~ s/, ([^,])/; $1/g; $_ =~ s/\\[,;]//g; my $job; my @columns = split(','); my $idx = 0; foreach my $label (@job_info_labels) { $job->{$label} = $columns[$idx]; $idx++; } my @files = (); if ($job->{'filelistcount'} > 0) { @files = @columns[$idx..$idx+$job->{'filelistcount'}-1]; } $job->{'files'} = [@files]; $idx += $job->{'filelistcount'} ; $job->{'trycount'} = $columns[$idx]; # Now get the try data. Within the try data # is a list of try status lines. my @trylist = (); $idx++; my ($trydata, @trystatuslines); for (1..$job->{'trycount'}) { undef($trydata); foreach my $trylabel ( @job_try_labels_1 ) { $trydata->{$trylabel} = $columns[$idx++]; if ($trylabel eq 'trystunit') { # Vault duplication jobs have, without warning, multiple # STUs here until ($columns[$idx + 1] =~ m/^[0-9]+$/) { $trydata->{$trylabel} .= "; $columns[$idx++]"; } } } @trystatuslines = @columns[$idx..$idx+$trydata->{'trystatuscount'}-1]; $trydata->{'trystatuslines'} = [@trystatuslines]; $idx += $trydata->{'trystatuscount'}; foreach my $trylabel ( @job_try_labels_2 ) { $trydata->{$trylabel} = $columns[$idx]; $idx++; } push @trylist, $trydata; } $job->{'trylist'} = [@trylist]; foreach my $vltlabel (@vault_info_labels) { $job->{$vltlabel} = $columns[$idx]; $idx++; } return $job; } my $daylength = (24 * 60 * 60); my $daywanted = 0; my $all = 0; my $active = 0; my @clients = (); my $client = undef; my @servers = (); my $server = undef; my @stunits = (); my $stunit = undef; my @types = (); my $type = undef; my @policies = (); my $policy = undef; my @schedules = (); my $schedule = undef; my @statuses = (); my $status = undef; my $help = 0; my $insensitive = 0; #my $noheader = 0; # 'noheader' => \$noheader, GetOptions('all' => \$all, 'active' => \$active, 'client=s' => \@clients, 'server=s' => \@servers, 'stunit=s' => \@stunits, 'type=s' => \@types, 'policy=s' => \@policies, 'schedule=s' => \@schedules, 'status=s' => \@statuses, 'insensitive' => \$insensitive, 'help' => \$help); @clients = split(/,/,join(',',@clients)); @servers = split(/,/,join(',',@servers)); @stunits = split(/,/,join(',',@stunits)); @types = split(/,/,join(',',@types)); @policies = split(/,/,join(',',@policies)); @schedules = split(/,/,join(',',@schedules)); @statuses = split(/,/,join(',',@statuses)); if ($insensitive > 0) { $insensitive = "-i " } else { $insensitive = " "; } # "--noheader don't print column headers\n", if ($help) { print "--all Display all jobs (overrides --active).\n", "--active Display only active jobs.\n", "--client [,...] Limit display to client(s) specified.\n", "--server [,...] Limit display to server(s) specified.\n", "--stunit [,...] Limit display to storage unit(s) specified.\n", "--type [,...] Limit display to job type(s) specified.\n", " Job types are: b[ackup], a[rchive], r[estore],\n", " [V]erify, [d]uplication, [i]mport, [c]atalog,\n", " [v]ault. Note that case matters.\n", "--policy [,...] Limit display to policy/ies specified.\n", "--schedule [,...] Limit display to schedule(s) specified.\n", "--status <1-522>[,...] Limit display to jobs ended with the\n", " status specified.\n", "--insensitive ignore case\n", "\n", "All options that take arguments may be specified repeatedly, or be\n", "supplied a comma-separated list of values. Limits of different types\n", "are ANDed, of the same type are ORed.\n"; exit 0; } my $earliest = time() - (($daywanted + 1) * $daylength); my $latest = time() - ($daywanted * $daylength); # Trimming out things we're going to ignore in the call to grep can # significantly reduce our memory footprint and execution time, so # I think it's worth the ugly. # # We'll just fill in regexps here, then fill that out to an element # of a pipeline down below. These regexps have to be useable by # egrep. my $overall_limit = ''; my $limit = ''; if ($active == 1) { # Anything with a status (third field) other than 3 ("Done") counts # as active. # - active (/[0-2]/ rather than a -v 3 because the match needs to be # positive, since we're doing a bunch, may add some down below $overall_limit = "| egrep '^[0-9]*,[0-9]?,[0-2]'"; } else { # This is, actually, our default case. Several possibilities # - active, as above # - with a status other 0 or 1 which, since egrep does chars, not # math, means either one-digit 2 through 9--/[2-9],/--or more # digits where the first is anything but 0--/[1-9][0-9]+,/. $overall_limit = "| egrep '^[0-9]*,[0-9]?,([0-2]|.?,[2-9],|.?,[1-9][0-9]+)'"; } # -all should override -active and the default, but not the stuff # below if ($all == 1) { $overall_limit = ''; } my $last = ''; foreach $type (@types) { $type = (split(/ */, $type))[0]; unless (defined($jt_codes_reverse{$type})) { print STDERR "Ignoring unknown job type $type.\n"; next; } $limit .= '|'; $limit .= " egrep '" unless ($last eq 'type'); $limit .= "^[0-9]*,$jt_codes_reverse{$type}"; $last = 'type'; } $limit .= "' " if ($last eq 'type'); foreach $status (@statuses) { $limit .= '|'; $limit .= " egrep '" unless ($last eq 'status'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,$status,"; $last = 'status'; } $limit .= "' " if ($last eq 'status'); foreach $policy (@policies) { $limit .= '|'; $limit .= " egrep $insensitive '" unless ($last eq 'policy'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,[0-9]*,$policy"; $last = 'policy'; } $limit .= "' " if ($last eq 'policy'); foreach $schedule (@schedules) { $limit .= '|'; $limit .= " egrep $insensitive '" unless ($last eq 'schedule'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,[0-9]*,[^,]*,$schedule"; $last = 'schedule'; } $limit .= "' " if ($last eq 'schedule'); foreach $client (@clients) { $limit .= '|'; $limit .= " egrep $insensitive '" unless ($last eq 'client'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,[0-9]*,[^,]*,[^,]*,$client"; $last = 'client'; } $limit .= "' " if ($last eq 'client'); foreach $server (@servers) { $limit .= '|'; $limit .= " egrep $insensitive '" unless ($last eq 'server'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,[0-9]*,[^,]*,[^,]*,[^,]*,$server"; $last = 'server'; } $limit .= "' " if ($last eq 'server'); foreach $stunit (@stunits) { $limit .= '|'; $limit .= " egrep $insensitive '" unless ($last eq 'stunit'); $limit .= "^[0-9]*,[0-9]?,[0-9]?,[0-9]*,[^,]*,[^,]*,[^,]*,[^,]*," . "[0-9]*,[0-9]*,[0-9]*,$stunit"; $last = 'stunit'; } $limit .= "' " if ($last eq 'stunit'); # fill out our regexp with the CL details, provided we've got one $limit = "$overall_limit $limit"; print "[$limit]\n" if $debug; #exit 0; my @bpdbjobs_lines = qx{$sudo /usr/openv/netbackup/bin/admincmd/bpdbjobs -most_columns $limit}; # = qx{$sudo /usr/openv/netbackup/bin/admincmd/bpdbjobs -all_columns $limit}; foreach (reverse @bpdbjobs_lines) { my $start = (split(',', $_))[8]; my $job = parse_line($_); # This should all be taken care of by $limit above. # if ( # ( # # Yes, I really had a job with these whacked like this. # # Note that jobs that fail here will still show up in a -all # # listing. # (! ($job->{'state'} =~ m/[0-9]/)) # || # (! ($job->{'status'} =~ m/[0-9]/)) # ) # || # ( # $all == 1 # || # ($active == 1 && $job->{'state'} != 3) # || # ($active == 0 && (($job->{'state'} != 3) || ($job->{'status'} > 1))) # ) # ) { my ($policy, $sched, $client, $hours, $minutes, $seconds, $meta); if ($job->{'jobtype'} =~ m/[0-9]/ && $job->{'jobtype'} == 7) { # (Vault) # $policy = $job->{'vltrobot'} . " " . $job->{'vltname'}; $policy = $job->{'vltname'}; $sched = $job->{'vltprofile'}; } else { # $policy = substr($job->{'class'}, 0, 24); # $sched = substr($job->{'sched'}, 0, 10); $policy = $job->{'class'}; $sched = $job->{'sched'}; } # $client = substr($job->{'client'}, 0, 8); $client = (split('\.', $job->{'client'}))[0]; $hours = ($job->{'elapsed'} - ($job->{'elapsed'} % (60 * 60))) / (60 * 60); $minutes = (($job->{'elapsed'} - ($job->{'elapsed'} % 60)) / 60) - ($hours * 60); $seconds = $job->{'elapsed'} - ($hours * 60 * 60) - ($minutes * 60); if (($job->{'state'} =~ m/[0-9]/) && ($job->{'state'} == 3)) { # done $meta = $job->{'status'}; } elsif (($job->{'state'} =~ m/[0-9]/) && ($job->{'state'} == 1)) { # active $meta = $op_codes{$job->{'operation'}}; } else { # requeued or queued $meta = $state_codes{$job->{'state'}}; } # $meta = '...' unless defined($meta); $meta = $job->{'operation'} unless defined($meta); # printf "%d %s %3s %20s %10s %8s %s %02d:%02d:%02d\n", printf "%d %s %3s %30s %8s %s %02d:%02d:%02d\n", $job->{'jobid'}, ($job->{'jobtype'} =~ m/[0-9]/) ? $jt_codes{$job->{'jobtype'}} : '?', $meta, ((defined($policy)) ? $policy : '(policy)') . " " . ((defined($sched)) ? $sched : '(sched)'), (defined($client)) ? $client : '(client)', strftime('%m%d %T', localtime($job->{'start'})), $hours, $minutes, $seconds; # } }