#!/usr/bin/perl -w use strict; use POSIX; use Data::Dumper; # divide seconds by this (time slice we'll display; we'll always do # math in seconds) my $slice = 60; #my $slice = 300; my $show_jobs = 0; my $show_merged = 0; # the parsing bits come from: # http://www.backupcentral.com/cgi-bin/netbackup-fom?_highlightWords=bpdbjobs&file=194 # constructing a real-world schedul from that is all gr # # take it use, don't blame me if it breaks something 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'); # Jacked from the (TOTALLY UNDOCUMENTED SO I CAN'T USE IT WITHOUT # LOTS OF PAIN) NBU::Job. my %op_codes = ( -1 => '---', 25 => 'Waiting', 2 => 'Connecting', 26 => 'Connecting', 0 => ' ? ', 27 => 'Mounting', 29 => 'Positioning', 3 => 'Writing', 35 => 'Writing', 5 => 'Duplicating', ); # 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', ); my %jt_codes_long = ( 0 => 'Backup', 1 => 'Archive', 2 => 'Restore', 3 => 'Verify', 4 => 'Duplication', 5 => 'Import', 6 => 'Catalog Backup', 7 => 'Vault', ); my %state_codes = ( 0 => 'Queued', 1 => 'Active', 2 => 'Requeued', 3 => 'Done', ); my ($job, @jobs, @columns, $idx, @files, @trylist, $trydata, @trystatuslines); while(<>) { chomp; # Yeah, they really include escaped ,s; ditch them # ... only but there are some legit \\, situations on Windows $_ =~ s#\\\\#//#g; $_ =~ s/\\,//g; undef($job); @columns = split(','); $idx = 0; foreach my $label (@job_info_labels) { # if ($label eq 'operation') { # $job->{$label} = $columns[$idx] . " (" . $op_codes{$columns[$idx]} . ")"; # } elsif ($label eq 'jobtype') { # $job->{$label} = $columns[$idx] . " (" . $jt_codes{$columns[$idx]} . ")"; # } elsif ($label eq 'state') { # $job->{$label} = $columns[$idx] . " (" . $state_codes{$columns[$idx]} . ")"; # } else { $job->{$label} = $columns[$idx]; # } $idx++; } @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. @trylist = (); $idx++; if ($job->{'trycount'} > 0) { for (1..$job->{'trycount'}) { undef($trydata); foreach my $trylabel ( @job_try_labels_1 ) { $trydata->{$trylabel} = $columns[$idx]; $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]; push @jobs, $job; } #print Dumper(@jobs); #exit 0; my $earliest = $jobs[0]->{'start'}; my $latest = $earliest + $jobs[0]->{'elapsed'}; #my $earliest = 0; #my $latest = 0; #foreach $job (@jobs) { # if ($jobs[0]->{'trycount'} > 0) { # print Dumper(@jobs); # $earliest = @{$jobs[0]->{'trylist'}}[0]->{'trystarted'}; # $latest = @{$jobs[0]->{'trylist'}}[0]->{'tryended'}; # last; # } #} #my $try; foreach $job (@jobs) { $earliest = $job->{'start'} if ($job->{'start'} < $earliest); $latest = $job->{'start'} + $job->{'elapsed'} if (($job->{'start'} + $job->{'elapsed'}) > $latest); # foreach $try (@{$job->{'trylist'}}) { # $earliest = $try->{'trystarted'} # if ($try->{'trystarted'} < $earliest); # $latest = $try->{'tryended'} # if ($try->{'tryended'} > $latest); # } } # Drop back to the beginning of the earliest time slice and the # end of the latest. $earliest -= ($earliest % $slice); $latest += $slice - ($latest % $slice); my $scale_end = ($latest - $earliest) / $slice; my (@schedule, $i); for ($i = 0; $i <= $scale_end; $i++) { my @sched_element; foreach (keys %jt_codes) { $sched_element[$_] = 0; } $schedule[$i] = \@sched_element; } #my ($span, $try_begin, $try_end); my ($span, $job_begin, $job_end); print "Time spans by job:\n" if ($show_jobs); foreach $job (reverse @jobs) { $job_begin = ($job->{'start'} - ($job->{'start'} % $slice) - $earliest) / $slice; # foreach $try (@{$job->{'trylist'}}) { # $try_begin = # ($try->{'trystarted'} # - ($try->{'trystarted'} % $slice) # - $earliest) # / $slice; $job_end = (($job->{'start'} + $job->{'elapsed'}) + ($slice - (($job->{'start'} + $job->{'elapsed'}) % $slice)) - $earliest) / $slice; # $try_end = # ($try->{'tryended'} # + ($slice - ($try->{'tryended'} % $slice)) # - $earliest) # / $slice; if ($show_jobs) { $span = $job->{'jobid'} . " "; for ($i = 0; $i < $job_begin; $i++) { $span .= '-'; } for ($i = $job_begin; $i <= $job_end; $i++) { $span .= $jt_codes{$job->{'jobtype'}}; } for ($i = $job_end + 1; $i <= $scale_end; $i++) { $span .= '-'; } print "$span\n"; } for ($i = $job_begin; $i <= $job_end; $i++) { $schedule[$i][$job->{'jobtype'}] = 1; } # } } print "\n" if ($show_jobs); my $time; if ($show_merged) { print "Time span by date (", $scale_end + 1, " rows of $slice\-second time slices):\n"; print "date | job type\n"; # print "date | "; # foreach (keys %jt_codes) { # print $jt_codes{$_}, " "; # } # print "\n"; print "-------------------------+----------------\n"; for ($i = 0; $i <= $scale_end; $i++) { if ($i % 5 == 0) { chomp($time = ctime(($i * $slice) + $earliest)); print "$time | "; } else { print " | "; } foreach (keys %jt_codes) { if ($schedule[$i][$_] > 0) { print $jt_codes{$_}, " "; } else { print " "; } } print "\n"; } # print "\njob types:\n"; # foreach (keys %jt_codes) { # print $jt_codes{$_}, " ", $jt_codes_long{$_}, "\n"; # } print "\n"; } my $quiet_begin = -1; # XXX set filter on CL my $min_span = 10; #my $min_span = 1; print "Open time windows longer than $min_span minute"; print "s" if ($min_span > 1); print ":\n"; TICK: for ($i = 0; $i <= $scale_end; $i++) { if ($quiet_begin < 0) { foreach (keys %jt_codes) { next TICK if ($schedule[$i][$_] > 0); } $quiet_begin = $i; } else { foreach (keys %jt_codes) { if ($schedule[$i][$_] > 0) { $span = ((($i - 1) - $quiet_begin) * $slice) / 60; if ($span >= $min_span) { chomp($time = ctime(($quiet_begin * $slice) + $earliest)); print "$time through "; chomp($time = ctime((($i - 1 ) * $slice) + $earliest)); print "$time ($span minutes)\n"; } $quiet_begin = -1; next TICK; } } } }