#!/usr/bin/perl use Data::Dumper; # Each line of bpdbjobs output has a fairly long, complicated format # described in the PDF: NetBackup Commands for UNIX and Linux. my $lineformat = [ 'jobid', 'jobtype', 'state', 'status', 'class', 'schedule', 'client', 'server', 'started', 'elapsed', 'ended', 'stunit', 'try', 'operation', 'kbytes', 'files', 'pathlastwritten', 'percent', 'jobpid', 'owner', 'subtype', 'classtype', 'schedule_type', 'priority', 'group', 'masterserver', 'retentionunits', 'retentionperiod', 'compression', 'kbyteslastwritten', 'fileslastwritten', 'filelistcount' => [ 'files', ], 'trycount', => [ 'trypid', 'trystunit', 'tryserver', 'trystarted', 'tryelapsed', 'tryended', 'trystatus', 'trystatusdescription', 'trystatuscount' => [ 'trystatuslines', ], 'trybyteswritten', 'tryfileswritten', ], # Fields below taken from DWR's python script on # Curtis Preston's backupcentral.com wiki. 'parentjob', 'kbpersec', 'copy', 'robot', 'vault', 'profile', 'session', 'ejecttapes', 'srcstunit', 'srcserver', 'srcmedia', 'dstmedia', 'stream', 'suspendable', 'resumable', 'restartable', 'datamovement', 'frozenimage', 'backupid', 'killable', 'controllinghost', # Plus one unaccounted for. 'unknown', ]; my %valuenames = ( 'jobtype' => [ 'backup', 'archive', 'restore', 'verify', 'duplication', 'import', 'dbbackup', 'vault', 'label', 'erase', 'tpreq', 'tpclean', 'tpformat', 'vmphyinv', 'dqts', 'dbrecover', 'mcontents', ], 'schedule_type' => [ 'FULL', 'INCR', 'UBAK', 'UARC', 'CINC', ], 'state' => [ 'queued', 'active', 'waiting for retry', 'done', ], 'subtype' => [ 'immediate', 'scheduled', 'user-initiated', ], ); sub parse_bpdbjobs { my ($fields, $format, $repeat) = @_; unless (ref $fields) { chomp $fields; # split on unescaped commas; perl has variable-length # lookahead but not lookbehind in regexps, so we work # with reversed strings, then un-reverse each field *and* # return the entire list of fields to normal order. $fields = [ reverse map { scalar reverse } # comma, NOT followed by: # a backslash, # followed by any even number of backslashes, # followed by a non-backslash or end-of-string. split(/,(?!\\(?:\\\\)*(?:[^\\]|$))/, scalar reverse($fields)) ]; } $format = $lineformat unless (defined $format); $repeat = 1 unless (defined $repeat); my @result; for (my $i = 0; $i < $repeat; $i++) { my %item = (); my $prev_field; foreach my $format_field ( @$format) { if (ref $format_field) { # sublist; expect number of repetitions given by the previous field my $sublist = parse_bpdbjobs($fields, $format_field, $prev_field); # hack: name the sublist by the first field in it $item{$format_field->[0]} = $sublist; } else { # scalar; convert to english if necessary and store it. my $field = shift @$fields; if (exists $valuenames{$format_field} && $field =~ /\d+/) { $field = $valuenames{$format_field}->[$field] || $field; } $item{$format_field} = $field; $prev_field = $field; } } # Single field (as in list of files or list of try status lines) # should not bother with encapsulation in a hash. if ( @$format == 1) { push @result, values %item; } else { push @result, \%item; } } return \ @result; } open JOBS, '-|', '/opt/openv/netbackup/bin/admincmd/bpdbjobs', '-all_columns' or die "Couldn't run bpdbjobs: $!"; while () { my $job = parse_bpdbjobs($_)->[0]; # Can now refer to: # $job->{'jobid'}, # $job->{'files'}->[0], # $job->{'trypid'}->[0]->{'trystatus'}, # $job->{'trypid'}->[0]->{'trystatuslines'}->[0], # etc. print Dumper($job); # ... Do other stuff with $job here ... } close JOBS;