#!/usr/bin/perl -w # NAME # cpu-actualization - Generate a cpu actualization report from Rush cpu.acct file # # SYNOPSIS # cpu-actualization [-asciigraph] [-spreadsheet] |- # # EXAMPLES # To accumulate data from a single machine: # cat /usr/local/rush/var/cpu.acct | perl cpu-actualization.pl - > foo.out # - or - # perl cpu-actualization.pl /usr/local/rush/var/cpu.acct > foo.out # # To accumulate data from ALL machines: # rush -catlog cpu.acct +any | perl cpu-actualization.pl - > foo.out # # 1.0 erco ??/??/?? -- initial implementation # 1.1 erco 04/21/05 -- added render time rollovers into next day # 1.2 erco 12/17/09 -- added midnight datestamp parsing # use POSIX; # GLOBALS my @G_dow = ( "Sun","Mon","Tue","Wed","Thu","Fri","Sat" ); my %G_cpus; my $G_hostfile = ( -e "/usr/local/rush/etc/hosts" ) ? "/usr/local/rush/etc/hosts" : "c:/rush/var/etc/hosts"; # CONVERT SECONDS TO HH:MM:SS sub Secs2HMS($) { my $secs = $_[0]; return(sprintf("%02d:%02d:%02d", $secs / 3600, ( $secs / 60 ) % 60, $secs % 60)); } # CONVERT TIME TO YYYY-MM-DD-WWW sub Time2YMDW($) { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($_[0]); return(sprintf("%04d/%02d/%02d-%s", $year+1900, $mon+1, $mday, $G_dow[$wday])); } # RETURN TIME NEXT DAY # Returns time at midnight next day. # $1 - time() # sub NextDay($) { my $time = $_[0]; $time += ( 3600 * 24 ); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $sec = 0; $min = 0; $hour = 0; return(POSIX::mktime($sec,$min,$hour,$mday,$mon,$year,0,0,0)); } # LOAD ACCOUNTING FILE sub LoadAccountDaily($$) { my ($acct, $filename) = @_; unless ( open (FD, "<$filename" ) ) { return("$filename: $!"); } my ($type,$stime,$jobid,$title,$name,$frame,$host,$pri,$wall,$sys,$usr,$exit,$pid) = (0,1,2,3,4,5,6,7,8,9,10,11,12); my $hostname; while ( ) { # HOSTNAME HEADER? if ( /^--- (\S+) cpu.acct/ ) { $hostname = $1; next; } my @fields = split(); if ( $fields[$type] eq 'p' ) { # PROCESSOR ENTRY # Example: # p 1260625771 ze.5 HONDA_SC24 erco 0001 tahoe 100 24 8 8 0 16373 # - ---------- ---- ---------- ---- ---- ----- --- -- - - - ----- # | | | | | | | | | | | | | # | | | | | | | | | | | | | # | | | | | | | | | | | | [12] Pid # | | | | | | | | | | | | # | | | | | | | | | | | [11] Exit code # | | | | | | | | | | | # | | | | | | | | | | [10] #Secs User Time # | | | | | | | | | | # | | | | [4] Owner| | | | [9] #Secs System Time # | | | | | | | | # | | | [3] Title of job | | | [8] #Secs Wall Clock Time # | | [2] Jobid | | | # | | | | [7] Priority # | [1] time process started | | # | (in unix time format) | [6] Host that ran the process # [0] 'p' indicates 'process entry' | # [5] Frame rendered my $time = $fields[$stime]; my $secs = $fields[$wall]; $hostname = $fields[$host]; # Day loop: Handle renders that run over 24 hours or cross midnight boundaries. while ( 1 ) { # YYYY/MM/DD-WWW my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); my $ymdw = sprintf("%04d/%02d/%02d-%s", $year+1900, $mon+1, $mday, $G_dow[$wday]); my $nextdaysecs = 0; my $daystart = $time - $hour * 3600 - $min * 60; my $dayend = $daystart + ( 24 * 3600 ); my $maxsecs = ( $dayend - $time ); # TRIM SECONDS TO NOT EXCEED END OF DAY if ( $secs > $maxsecs ) { $nextdaysecs = ( $secs - $maxsecs ); $secs = $maxsecs; } # HOST UTILIZATION # Do not count seconds that wrap into next day # $$acct{host}{$ymdw}{$fields[$host]} += $secs; # JOB TOTALS $$acct{show}{$ymdw}{$fields[$title]} += $secs; # USER TOTALS $$acct{user}{$fields[$name]} += $secs; # NO OVERRUNS? if ( $nextdaysecs == 0 ) { last; } # OVERRUN INTO NEXT DAY $secs = $nextdaysecs; $daystart = $time = NextDay($time); } } elsif ( $fields[$type] eq 'm' ) { # MIDNIGHT MARKER? MAKE SURE AN ENTRY APPEARS FOR THAT DAY # Example: # m 940100000 # | | # | time stamp # midnight marker # my $time = $fields[1]; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); my $ymdw = sprintf("%04d/%02d/%02d-%s", $year+1900, $mon+1, $mday, $G_dow[$wday]); $$acct{host}{$ymdw}{$hostname} += 0; } } if ( $filename ne "-" ) { close(FD); } } # RETURN AN 'ASCII GRAPH' OF THE VALUE # $1 - value (eg. 10) # $2 - maxvalue (eg. 20) # $3 - width of ascii graph (eg. 8) # Returns: an ascii chart. With above numbers, result="####...." # sub AsciiGraph($$$) { my ($val, $maxval, $width) = @_; # print "VAL=$val, MAXVAL=$maxval, WIDTH=$width\n"; if ( $val > $maxval ) { $val = $maxval; } if ( $maxval == 0 ) { $maxval = 1; $val = 1; } my $busy = $val / $maxval; my $hash_busy = int($width * $busy + .5); if ( $val > 0 && $hash_busy == 0 ) { $hash_busy = 1; } # non-zero? show at least one '#' my $hash_idle = $width - $hash_busy; return("#"x$hash_busy . "."x$hash_idle); } ### MAIN { my %acct; my $asciigraph = 0; my $spreadsheet = 0; my @filenames; # PARSE ARGS foreach ( @ARGV ) { if ( $_ eq "-asciigraph" ) { $asciigraph = 1; next; } if ( $_ eq "-spreadsheet" ) { $spreadsheet = 1; next; } push(@filenames, $_); } # NO FILENAMES SPECIFIED? STDIN DEFAULT if ( $#filenames < 0 ) { push(@filenames, "-"); } # LOAD DATA FROM ALL FILENAMES (OR STDIN) foreach ( @filenames ) { LoadAccountDaily(\%acct, $_ ); } { print "--- HOST DAILY UTILIZATION\n"; if ( $spreadsheet ) { printf("%s\t%s\t%s\n", "Date", "Host", "TimeBusy"); } else { printf("%-14s %-15s %9s%s\n", "Date", "Host", "TimeBusy", ($asciigraph)?" Graph":""); } my $ymdw; foreach $ymdw ( sort ( keys ( %{ $acct{host} } ) ) ) { my $host = ""; my $cpus = 1; # TODO: get this from hosts file foreach $host ( sort ( keys ( %{ $acct{host}{$ymdw} } ) ) ) { my $secs = $acct{host}{$ymdw}{$host}; if ( $asciigraph ) { printf("%-14s %-15s %9s [%s]\n", $ymdw, $host, Secs2HMS($secs), AsciiGraph($secs, (86400*$cpus), 40)); } elsif ( $spreadsheet ) { printf("%s\t%s\t%s\n", $ymdw, $host, Secs2HMS($secs)); } else { printf("%-14s %-15s %9s\n", $ymdw, $host, Secs2HMS($secs)); } } } } { print "\n--- JOB DAILY TOTALS ---\n"; if ( $spreadsheet ) { printf("%s\t%s\t%s\n", "Date", "JobTitle", "TimeBusy"); } else { printf("%-14s %-65s %9s%s\n", "Date", "JobTitle", "TimeBusy", ($asciigraph)?" Graph":""); } my $ymdw; foreach $ymdw ( sort ( keys ( %{ $acct{show} } ) ) ) { my $cpus = 1; # TODO: get this from hosts file my $show = ""; foreach $show ( sort ( keys ( %{ $acct{show}{$ymdw} } ) ) ) { my $secs = $acct{show}{$ymdw}{$show}; if ( $asciigraph ) { printf("%-14s %-65s %9s [%s]\n", $ymdw, $show, Secs2HMS($secs), AsciiGraph($secs, (86400*$cpus), 40)); } elsif ( $spreadsheet ) { printf("%s\t%s\t%s\n", $ymdw, $show, Secs2HMS($secs)); } else { printf("%-14s %-65s %9s\n", $ymdw, $show, Secs2HMS($secs)); } } } } # TODO: USER DAILY TOTALS # TODO: Make the above reports optional, eg. -hostreport, -jobreport, -userreport # TODO: Make reports -daily or -weekly }