#! /usr/bin/perl -w ################################################################################ # # showjobs - list historical job information # # Copyright (C) 2010 by Adaptive Computing Enterprises, Inc. All Rights Reserved. # # To enable job logging set the TORQUE server parameter record_job_info to TRUE # Edit this file to set $torqueHomeDir to the Torque home directory # Move or link to this file in a directory in your user's path # Run showjobs --help for a brief help message or showjobs --man for a man page # ################################################################################ use strict; use Getopt::Long 2.24 qw(:config no_ignore_case); use Time::Local; use autouse 'Pod::Usage' => qw(pod2usage); use subs qw(ddhhmmsss epochdate); # Set $torqueHomeDir to the Torque home directory my $torqueHomeDir = "/var/spool/torque"; # Parse Command Line Arguments my ( $account, $endDate, $full, $group, $help, $man, $num, $queue, $specifiedJobId, $startDate, $user, $oneonly ); GetOptions( 'account=s' => \$account, 'queue=s' => \$queue, 'endDate=s' => \$endDate, 'full' => \$full, 'group=s' => \$group, 'help|?' => \$help, 'jobid=s' => \$specifiedJobId, 'man' => \$man, 'num=i' => \$num, 'startDate=s' => \$startDate, 'user=s' => \$user, 'oneonly' => \$oneonly, ) or pod2usage(2); # Display usage if necessary pod2usage(2) if $help; if ($man) { if ($< == 0) # Cannot invoke perldoc as root { my $id = eval { getpwnam("nobody") }; $id = eval { getpwnam("nouser") } unless defined $id; $id = -2 unless defined $id; $< = $id; } $> = $<; # Disengage setuid $ENV{PATH} = "/bin:/usr/bin"; # Untaint PATH delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; if ($0 =~ /^([-\/\w\.]+)$/) { $0 = $1; } # Untaint $0 else { die "Illegal characters were found in \$0 ($0)\n"; } pod2usage(-exitstatus => 0, -verbose => 2); } # Use remaining argument as job id if (@ARGV) { if (@ARGV == 1 && ! $specifiedJobId) { ($specifiedJobId) = @ARGV; } else { pod2usage(2); } # treat brackets as literal in job id for array jobs $specifiedJobId =~ s/\[/\\\[/g; $specifiedJobId =~ s/\]/\\\]/g; } # Build a sorted list of job files chdir("${torqueHomeDir}/job_logs") or die "Unable to change directory to job_logs directory (${torqueHomeDir}/job_logs): $!\n"; my @jobFiles = glob("20*"); if ($specifiedJobId) { @jobFiles = reverse sort @jobFiles; # search from most recent log if a particular jobs is specified } else { @jobFiles = sort @jobFiles; } if ($startDate) { if ($startDate =~ /^(\d{4})[\/\-](\d{2})[\/\-](\d{2})$/) { my $startEpoch = timelocal(0, 0, 0, $3, $2 - 1, $1 - 1900); @jobFiles = grep { epochDate($_) >= $startEpoch } @jobFiles; } else { die "Start Date ($startDate) is not in YYYY-MM-DD format.\n"; } } if ($endDate) { if ($endDate =~ /^(\d{4})[\/\-](\d{2})[\/\-](\d{2})$/) { my $endEpoch = timelocal(0, 0, 0, $3, $2 - 1, $1 - 1900); @jobFiles = grep { epochDate($_) <= $endEpoch } @jobFiles; } else { die "End Date ($endDate) is not in YYYY-MM-DD format.\n"; } } if ($num) { my ($firstIndex, $lastIndex); if ($specifiedJobId) { $firstIndex = 0; $lastIndex = ($num < $#jobFiles) ? $num : $#jobFiles; } else { $firstIndex = ($num <= @jobFiles) ? @jobFiles - $num : 0; $lastIndex = $#jobFiles; } @jobFiles = @jobFiles[$firstIndex .. $lastIndex]; } # Parse the job files and populate %job # Torque job attribute names are found in src/include/pbs_ifl.h # This will be done via manual XML parsing for performance reasons my %jobAttr = (); my @jobs = (); my $context = ''; OUTER: foreach my $jobFile (@jobFiles) { open JOBS, "< $jobFile" or die "Unable to open job file ($jobFile) for reading: $!\n"; my @lines = ; while (defined(my $line = shift @lines)) { chomp $line; if ($line =~ //) { # Initialize %jobAttr = (); $context = 'JobInfo'; } elsif ($line =~ /([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)/) { $context = 'Resource_List'; } elsif ($line =~ /<\/Resource_List>/) { $context = 'JobInfo'; } elsif ($line =~ //) { $context = 'resources_used'; } elsif ($line =~ /<\/resources_used>/) { $context = 'JobInfo'; } elsif ($line =~ /([^<]*)/) { my $jobScript = $1; while (defined($line = shift @lines)) { if ($line =~ /<\/job_script>/) { last; } else { $jobScript .= "\n$line"; } } $jobAttr{'Job Script'} = $jobScript; } elsif ($line =~ /([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)([^<]+)/) { # Finalize $context = ''; next if ( defined $specifiedJobId && (! defined $jobAttr{'Job Id'} || $jobAttr{'Job Id'} !~ /^${specifiedJobId}(?:.|\b)/) ); next if ( defined $user && (! defined $jobAttr{'User Name'} || $jobAttr{'User Name'} !~ /^$user$/i) ); next if ( defined $group && (! defined $jobAttr{'Group Name'} || $jobAttr{'Group Name'} !~ /^$group$/i) ); next if ( defined $account && (! defined $jobAttr{'Account Name'} || $jobAttr{'Account Name'} !~ /^$account$/i) ); next if ( defined $queue && (! defined $jobAttr{'Queue Name'} || $jobAttr{'Queue Name'} !~ /^$queue$/i) ); my %jobAttrCopy = %jobAttr; push @jobs, \%jobAttrCopy; } last OUTER if @jobs and $oneonly; } } # Print out the jobs foreach my $job (@jobs) { my %jobAttr = %{$job}; foreach my $name ( 'Job Id', 'Job Name', 'Output File', 'Error File', 'Working Directory', 'Home Directory', 'Submit Arguments', 'User Name', 'Group Name', 'Account Name', 'Queue Name', 'Quality Of Service', 'Architecture', 'Operating System', 'Node Count', 'Wallclock Limit', 'Wallclock Duration', 'CPUTime', 'Memory Used', 'Memory Limit', 'vmem Used', 'vmem Limit', 'Submit Time', 'Start Time', 'End Time', 'Exit Code', 'Master Host', 'Interactive', 'Job Dependencies', 'Job Script', ) { if (defined $jobAttr{$name}) { my $value = $jobAttr{$name}; if ($name =~ / Time$/) { $value = localtime $value; } elsif ($name =~ /Wallclock/) { $value = ddhhmmss($value); } elsif ($name =~ /CPUTime/) { $value = ddhhmmss($value); } printf "%-18s: %s\n", $name, $value; delete $jobAttr{$name}; } } if ($full) { foreach my $name (sort keys %jobAttr) { my $value = $jobAttr{$name}; printf "%-18s: %s\n", $name, $value; } } print '-' x 80, "\n"; print "\n"; } # Exit with status code exit 0; ################################################################################ # $epochDate = epochDate($jobFile) # Returns the epoch date for a job file ################################################################################ sub epochDate { my ($jobFile) = @_; $jobFile =~ s/.*\///; my ($year, $mon, $day) = unpack "A4A2A2", $jobFile; my $epochDate = timelocal(0, 0, 0, $day, $mon - 1, $year - 1900); return $epochDate; } ################################################################################ # $hRTime = toHRT($epochTime) # Converts an epoch time to a human readable time ################################################################################ sub hrTime { my ($jobFile) = @_; $jobFile =~ s/.*\///; my ($year, $mon, $day) = unpack "A4A2A2", $jobFile; my $epochDate = timelocal(0, 0, 0, $day, $mon - 1, $year - 1900); return $epochDate; } ################################################################################ # $ddhhmmss = ddhhmmss($seconds) # Converts a duration in seconds to dd:hh:mm:ss ################################################################################ sub ddhhmmss { my ($seconds) = @_; # Convert duration in minutes to ddhhmm my $days = int($seconds / 86400); $seconds = $seconds % 86400; my $hours = int($seconds / 3600); $seconds = $seconds % 3600; my $minutes = int($seconds / 60); $seconds = $seconds % 60; if ($days) { return sprintf('%d:%02d:%02d:%02d', $days, $hours, $minutes, $seconds); } else { return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); } } ################################################################################ # $ktoMG = ktoMG($kilo) # Converts kilo units to mega or giga (mebi and gibi) # k, M and G are used rather than the more correct Ki, Mi and Gi ################################################################################ sub ktoMG { my ($value) = @_; if ($value =~ /^\s*(\d+)k/) { my $k = $1; if ($k > 1024) { my $M = $k / 1024; if ($M > 1024) { my $G = $M / 1024; $G = sprintf("%.1f", $G); $value =~ s/${k}k/${G}G/; } else { $M = sprintf("%.1f", $M); $value =~ s/${k}k/${M}M/; } } } return $value; } ############################################################################## __END__ =head1 NAME B - list historical job information =head1 SYNOPSIS B [B<-u> I] [B<-g> I] [B<-a> I] [B<-q> I] [B<-s> I] [B<-e> I] [B<-n> I] [B<-o|--oneonly>] [B<--help>] [B<--man>] [[B<-j>] ] =head1 DESCRIPTION The B command is used to list past job information. It searches through the designated job files while filtering according to the specified options. The relevant fields for each job are shown in a multi-line format, with a blank line between jobs. =head1 OPTIONS =over 4 =item B<-a> I Show only job records matching the specified account. =item B<-e> I Restricts the search to job files ending with the specified date. The date is specified in the format YYYY-MM-DD. The default query searches to the latest available job file. =item B<-g> I Show only job records matching the specified group. =item [B<-j>] I Show only job records matching the specified job id. =item B<-n> I Restricts the number of past job files to search. =item B<-q> I Show only job records matching the specified queue. =item B<-s> I Restricts the search to job files starting with the specified date. The date is specified in the format YYYY-MM-DD. The default query searches from the earliest available job file. =item B<-u> I Show only job records matching the specified user. =item B<-o | --oneonly> Show only the first job record found. This will mostly be much faster and give the same result as if the flag is omitted if the search is for a specific non-array job or specific array job member. =item B<-? | --help> brief help message =item B<--man> full documentation =back =head1 EXAMPLE Show job information for job id 220 and restrict the search to the last 4 days. showjobs -n 4 -j 220 =head1 AUTHOR Adaptive Computing, Inc., Ehttp://www.adaptivecomputing.com/E =head1 COPYRIGHT AND LICENSE Copyright (C) 2010 by Adaptive Computing Enterprises, Inc. All Rights Reserved. =cut