518 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			518 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/perl -w
 | |
| use strict;
 | |
| use Getopt::Std qw( getopts );
 | |
| use POSIX qw( );
 | |
| 
 | |
| ( my $Script = $0 ) =~ s{^.*/}{};
 | |
| 
 | |
| my $time = time;
 | |
| my $date = POSIX::strftime( "%F %T", localtime $time );
 | |
| 
 | |
| $ENV{SGE_ROOT} or die "Error $Script: \$SGE_ROOT is not set\n";
 | |
| $ENV{SGE_CELL} ||= "default";    # sge_cell
 | |
| 
 | |
| my ($siteLocation) = join "/" => ( $ENV{SGE_ROOT}, $ENV{SGE_CELL}, "site" );
 | |
| 
 | |
| my $config = {
 | |
|     -site   => ( $siteLocation || '' ),
 | |
|     -cache  => ( $siteLocation || '' ) . "/cache/" . "qlicserver.xml",
 | |
|     -limits => ( $siteLocation || '' ) . "/" . "qlicserver.limits",
 | |
| };
 | |
| 
 | |
| # ------------------------------------------------------------------------
 | |
| # utils
 | |
| 
 | |
| #
 | |
| # calculate age from an epoch value
 | |
| #
 | |
| sub age {
 | |
|     my ( $a, $b ) = @_;
 | |
|     my $diff = ( $a - $b );
 | |
| 
 | |
|     my $sign = '';
 | |
|     if ( $diff < 0 ) {    # handle negatives
 | |
|         $sign = '-';
 | |
|         $diff = abs($diff);
 | |
|     }
 | |
| 
 | |
|     sprintf "$sign%d:%02d:%02d",    # format into hh:mm:ss
 | |
|       ( int $diff / 3_600 ),        # hh
 | |
|       ( ( int $diff / 60 ) % 60 ),  # mm
 | |
|       ( $diff % 60 );               # ss
 | |
| }
 | |
| 
 | |
| #
 | |
| # change hash references to a comma-delimited string of key=value entries
 | |
| #
 | |
| sub hashRefToString {
 | |
|     map {
 | |
|         my $ref = $_;
 | |
|         ref $ref ? map { "$_=$ref->{$_}" } sort keys %$ref : ()
 | |
|     } @_;
 | |
| }
 | |
| 
 | |
| #
 | |
| # extract attrib="value" ... attrib="value"
 | |
| #
 | |
| sub parseXMLattrib {
 | |
|     my $str = shift || '';
 | |
|     my %attr;
 | |
| 
 | |
|     while ($str =~ s{^\s*(\w+)=\"([^\"]*)\"}{}s
 | |
|         or $str =~ s{^\s*(\w+)=\'([^\']*)\'}{}s )
 | |
|     {
 | |
|         $attr{$1} = $2;
 | |
|     }
 | |
| 
 | |
|     %attr;
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------
 | |
| # processing of qlicserver.xml output
 | |
| #
 | |
| package qlicCache;
 | |
| 
 | |
| sub new {
 | |
|     my $caller = shift;
 | |
|     my $file   = shift;
 | |
| 
 | |
|     my $self = bless {
 | |
|         ( map { $_ => {} } qw( -rc ) ),
 | |
|         ( map { $_ => '' } qw( -date -host -age ) )
 | |
|     }, $caller;
 | |
| 
 | |
|     -f $file or return $self;
 | |
| 
 | |
|     local $_ = do { local ( @ARGV, $/ ) = $file; <>; };
 | |
| 
 | |
|     # strip out all xml comments
 | |
|     s{<!--.*?-->\s*}{}sg;
 | |
| 
 | |
|     # get the header section
 | |
|     s{^(.+?)</query>}{}s or return;
 | |
|     my $header = $1;
 | |
| 
 | |
|     for ($header) {
 | |
|         if (m{<host (?:\s+([^<>]*))? > \s*(.+?)\s* </host>}sx) {
 | |
|             my ( $attr, $content ) = ( $1, $2 );
 | |
|             $self->{-host} = $content;
 | |
|         }
 | |
|         if (m{<time (?:\s+([^<>]*))? > \s*(.+?)\s* </time>}sx) {
 | |
|             my ( $attr, $content ) = ( $1, $2 );
 | |
|             my %attr = main::parseXMLattrib($attr);
 | |
| 
 | |
|             $self->{-age} = main::age( $time, $attr{epoch} ) if $attr{epoch};
 | |
|             $self->{-date} = $content;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     # only retain the <resources> contents
 | |
|     s{^.*?<resources>}{}s and s{</resources>.*$}{}s or return;
 | |
| 
 | |
|     ## process <resource .../>
 | |
|     while (s{<resource (?:\s+([^/<>]*))? />}{}sx) {
 | |
|         my ($attr) = ($1);
 | |
|         my %attr   = main::parseXMLattrib($attr);
 | |
|         my $name   = delete $attr{name};
 | |
| 
 | |
|         if ( defined $name ) {
 | |
|             for ( keys %attr ) {
 | |
|                 $self->{-rc}{$name}{$_} = $attr{$_} || 0;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     ## process <resource ...> CONTENT </resource>
 | |
|     while (s{<resource (?:\s+([^<>]*))? > \s* (.*?) \s* </resource>}{}sx) {
 | |
|         my ( $attr, $content ) = ( $1, $2 );
 | |
|         my %attr = main::parseXMLattrib($attr);
 | |
|         my $name = delete $attr{name};
 | |
| 
 | |
|         if ( defined $name ) {
 | |
|             for ( keys %attr ) {
 | |
|                 $self->{-rc}{$name}{$_} = $attr{$_} || 0;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ## process <user ...> COUNT </user>
 | |
|         while ( $content =~ s{<user (?:\s+([^<>]*))? >\s*(\d+)\s*</user>}{}sx )
 | |
|         {
 | |
|             my ( $attr, $count ) = ( $1, $2 );
 | |
|             my %attr = main::parseXMLattrib($attr);
 | |
|             my $user = delete $attr{name};
 | |
|             my $host = delete $attr{host};
 | |
| 
 | |
|             if ( defined $user ) {
 | |
|                 if ( defined $host ) {
 | |
|                     $self->{-rc}{$name}{-where}{"$user\@$host"} = $count;
 | |
|                 }
 | |
|                 else {
 | |
|                     ## tag waiting with '[]'
 | |
|                     $self->{-rc}{$name}{-where}{$user} = "[$count]";
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| sub query {
 | |
|     my $self = shift;
 | |
|     %{ $self->{-rc} };
 | |
| }
 | |
| 
 | |
| sub dump {
 | |
|     use Data::Dumper;
 | |
|     print Dumper( $_[0] );
 | |
| }
 | |
| 
 | |
| sub available {
 | |
|     my $self = shift;
 | |
|     sort keys %{ $self->{-rc} };
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------
 | |
| # processing of qlicserver.limits FILE or DIRECTORY
 | |
| #
 | |
| package qlicLimits;
 | |
| 
 | |
| sub new {
 | |
|     my $caller     = shift;
 | |
|     my $diskValues = shift;
 | |
| 
 | |
|     my $self = bless {}, $caller;
 | |
| 
 | |
|     my $fileString;
 | |
|     if ( defined $diskValues and -f $diskValues ) {
 | |
|         $fileString = do {
 | |
|             local ( *FILE, $/ );
 | |
| 
 | |
|             if ( open FILE, $diskValues ) {
 | |
|                 <FILE>;
 | |
|             }
 | |
|             else {
 | |
|                 undef;
 | |
|             }
 | |
|         };
 | |
|     }
 | |
|     elsif ( defined $diskValues and -d $diskValues ) {
 | |
|         local *DIR;
 | |
|         my $dir = $diskValues;
 | |
|         if ( opendir DIR, $dir ) {
 | |
|             my @files = grep { -f "$dir/$_" and -r _ } readdir DIR;
 | |
|             for my $name (@files) {
 | |
|                 my $limit;
 | |
| 
 | |
|                 # use the last value
 | |
|                 if ( open FILE, "$dir/$name" ) {
 | |
|                     $limit = ( map { /^\s*(-?\d+)\s*$/ } <FILE> )[-1];
 | |
|                 }
 | |
|                 defined $limit or $limit = "NONE";
 | |
|                 $self->{$name} = $limit;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for ($fileString) {
 | |
|         defined or next;
 | |
| 
 | |
|         # strip out all comments
 | |
|         s{<!--.*?-->\s*}{}sg;
 | |
| 
 | |
|         ## process <resource .../> and <resource ...></resource>
 | |
|         while (s{<resource (?:\s+([^/<>]*))? />}{}sx
 | |
|             or s{<resource (?:\s+([^/<>]*))? >\s*</resource>}{}sx )
 | |
|         {
 | |
|             my %attr  = main::parseXMLattrib($1);
 | |
|             my $name  = delete $attr{name};
 | |
|             my $limit = delete $attr{limit};
 | |
|             if ( defined $name and defined $limit ) {
 | |
|                 $self->{$name} = $limit;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return $self;
 | |
| }
 | |
| 
 | |
| sub change {
 | |
|     my $caller     = shift;
 | |
|     my $diskValues = shift;
 | |
|     my @args       = @_;
 | |
| 
 | |
|     @args or return;
 | |
| 
 | |
|     my ( %pending, %adjusted );
 | |
| 
 | |
|     for (@args) {
 | |
|         s{,}{ }g;    # comma -> space-delimited
 | |
| 
 | |
|         my %h = map { /^(.+?)=(.*)$/ } split;
 | |
|         for ( keys %h ) {
 | |
|             defined $h{$_} and length $h{$_} or $h{$_} = "NONE";
 | |
|             $pending{$_} = $h{$_};
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     keys %pending or return;
 | |
| 
 | |
|     my $user = getpwuid $<;
 | |
|     if ( defined $diskValues and -d $diskValues ) {
 | |
|         local *DIR;
 | |
|         my $dir = $diskValues;
 | |
|         if ( opendir DIR, $dir ) {
 | |
|             my @files = grep { -f "$dir/$_" and -w _ } readdir DIR;
 | |
| 
 | |
|             for my $name (@files) {
 | |
|                 if ( exists $pending{$name} ) {
 | |
|                     local *FILE;
 | |
|                     if ( open FILE, ">", "$dir/$name" ) {
 | |
|                         print FILE "# adjusted by $user $date\n",
 | |
|                           "$pending{$name}\n";
 | |
|                         $adjusted{$name} = delete $pending{$name};
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     for ( [ "adjusted" => \%adjusted ], [ "not adjusted" => \%pending ], ) {
 | |
|         my ( $label, $href ) = @$_;
 | |
|         keys %$href or next;
 | |
|         print "$label: ",
 | |
|           join( "," => map { qq{$_=$href->{$_}} } sort keys %$href ), "\n";
 | |
|     }
 | |
| }
 | |
| 
 | |
| sub output {
 | |
|     my $self = shift;
 | |
| 
 | |
|     my @list = map { qq{$_=$self->{$_}} } sort keys %$self;
 | |
| 
 | |
|     print "limits: ";
 | |
|     if (@list) {
 | |
|         print join( "," => @list ), "\n";
 | |
|     }
 | |
|     else {
 | |
|         print "NONE\n";
 | |
|     }
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------
 | |
| # main
 | |
| #
 | |
| 
 | |
| package main;
 | |
| import qlicCache;
 | |
| 
 | |
| # ------------------------------------------------------------------------
 | |
| sub usage {
 | |
|     $! = 0;    # clean exit
 | |
|     warn "@_\n" if @_;
 | |
|     die <<"USAGE";
 | |
| usage:
 | |
|     $Script [OPTION]
 | |
|     $Script [OPTION] resource=limit .. resource=limit
 | |
| 
 | |
| with options:
 | |
|   -c FILE  alternative location for the license cache
 | |
|   -C FILE  alternative location for the license limit
 | |
|   -d       dump cache file as raw xml
 | |
|   -D       dump license cache in perl format
 | |
|   -f       display free licenses only
 | |
|   -l       list license limit
 | |
|   -q       display free licenses via qhost query
 | |
|   -u       license usage via 'lacct'
 | |
|   -U       license usage per user via 'lacct -u'
 | |
|   -w       show who/where ('[]' indicates waiting jobs)
 | |
|   -h       this help
 | |
| 
 | |
| * extract / display information for the GridEngine license cache
 | |
|   $config->{-cache}
 | |
| 
 | |
| * adjust / display information for the license limits
 | |
|   $config->{-limits}
 | |
| 
 | |
| copyright (c) 2003-10 <Mark.Olesen\@faurecia.com>
 | |
| 
 | |
| Licensed and distributed under the Creative Commons
 | |
| Attribution-NonCommercial-ShareAlike 3.0 License.
 | |
| http://creativecommons.org/licenses/by-nc-sa/3.0
 | |
| USAGE
 | |
| }
 | |
| 
 | |
| # -------------------------------------------------------------------------
 | |
| my %opt;
 | |
| getopts( 'hc:C:DdflqUuw', \%opt ) or usage();
 | |
| $opt{h} and usage();
 | |
| 
 | |
| if ( $opt{U} ) {
 | |
|     ## user-based usage from accounting
 | |
|     my @query = ( "lacct", "-u" );
 | |
|     exec @query;
 | |
|     exit 2;
 | |
| }
 | |
| elsif ( $opt{u} ) {
 | |
|     ## usage from accounting
 | |
|     my @query = ( "lacct", $opt{w} ? "-w" : () );
 | |
|     exec @query;
 | |
|     exit 2;
 | |
| }
 | |
| 
 | |
| if ( $opt{d} ) {
 | |
|     my $file = $opt{c} || $config->{-cache};
 | |
| 
 | |
|     if ( not -f $file and $file !~ m{/} ) {
 | |
|         $file = "$config->{-site}/$file";
 | |
|     }
 | |
| 
 | |
|     -f $file or exit 1;
 | |
| 
 | |
|     local $_ = do { local ( @ARGV, $/ ) = $file; <>; };
 | |
| 
 | |
|     # do a basic check for well-formed xml
 | |
|     # this might not be the case if there is a race condition
 | |
|     # and the file has not been fully written
 | |
| 
 | |
|     # check for '<?xml version="1.0"?><someTag ...' at the start
 | |
|     # and '</someTag>' at the end
 | |
|     my ($tag) = m{^<\?xml (?:\s+[^<>]*?)? \?>\s*\<(\w+)}sx;
 | |
|     unless ( $tag and m{</\Q$tag\E>\s*$} ) {
 | |
|         sleep 2;    ## wait and try again
 | |
|         $_ = do { local ( @ARGV, $/ ) = $file; <>; };
 | |
|     }
 | |
| 
 | |
|     $_ ||= '';      ## avoid uninitialized
 | |
| 
 | |
|     print;
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| if ( $opt{q} ) {
 | |
|     my %free =
 | |
|       map  { /^\s+gc:(\S+?)=(\d\S*)\s*$/ }
 | |
|       grep { /^global/ ... /^\S/ } qx{qhost -F};
 | |
| 
 | |
|     $_ += 0 for values %free;
 | |
| 
 | |
|     for ( sort keys %free ) {
 | |
|         my $intval = $free{$_} + 0;    # normalize as integers
 | |
|         print "$_=$intval\n";
 | |
|     }
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| if ( $opt{l} ) {
 | |
|     qlicLimits->new( $opt{C} || $config->{-limits} )->output();
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| if (@ARGV) {
 | |
|     qlicLimits->change( $opt{C} || $config->{-limits}, @ARGV );
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| my $info = qlicCache->new( $opt{c} || $config->{-cache} );
 | |
| $info->dump() if $opt{D};
 | |
| 
 | |
| my %resources = $info->query();
 | |
| 
 | |
| #
 | |
| # display free licenses
 | |
| #
 | |
| if ( $opt{f} ) {
 | |
|     for my $resource ( sort keys %resources ) {
 | |
|         my $count = $resources{$resource} || {};
 | |
|         my $free  = $count->{free}        || 0;
 | |
|         print "$resource=$free\n" if $free;
 | |
|     }
 | |
| 
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| #
 | |
| # display who/where
 | |
| #
 | |
| if ( $opt{w} ) {
 | |
|     my ($len) = sort { $b <=> $a } map { length } keys %resources;
 | |
|     $len += 2;
 | |
| 
 | |
|     my $fmt    = "%-${len}s";
 | |
|     my $indent = sprintf "\n$fmt", '';
 | |
| 
 | |
|     for my $resource ( sort keys %resources ) {
 | |
|         my $count = $resources{$resource} || {};
 | |
|         my @list = hashRefToString( $count->{-where} );
 | |
|         if (@list) {
 | |
|             printf $fmt, $resource;
 | |
|             print join( $indent => @list ), "\n";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| #
 | |
| # define table output format
 | |
| #
 | |
| my @outputList;
 | |
| 
 | |
| format =
 | |
| # name      total limit extern intern wait free
 | |
| @<<<<<<<<<<< @>>>>> @>>>>> @>>>>> @>>>>> @>>>> @>>>>
 | |
| @outputList
 | |
| .
 | |
| 
 | |
| #
 | |
| # display table header
 | |
| # --------------------
 | |
| {
 | |
|     my @info = qw( host age );
 | |
|     my ($len) = sort { $b <=> $a } map { length } @info;
 | |
| 
 | |
|     print map {
 | |
|         my $k = sprintf "%-${len}s", $_;
 | |
|         my $v = $info->{"-$_"};
 | |
|         $v ? "$k = $v\n" : ();
 | |
|     } @info;
 | |
| 
 | |
|     print "\n";
 | |
| 
 | |
|     @outputList = qw( resource total limit extern intern wait free );
 | |
| 
 | |
|     write;
 | |
|     s/./-/g for @outputList;
 | |
|     write;
 | |
| }
 | |
| 
 | |
| #
 | |
| # display table body
 | |
| # ------------------
 | |
| for my $resource ( sort keys %resources ) {
 | |
|     my $count = $resources{$resource} || {};
 | |
|     @outputList =
 | |
|       ( $resource, @{$count}{qw( total limit extern intern waiting free )} );
 | |
| 
 | |
|     my $type = $resources{$resource}{type} || 0;
 | |
| 
 | |
|     # no total?
 | |
|     $_ ||= "?" for $outputList[1];
 | |
| 
 | |
|     if ( $type eq "intern" ) {
 | |
|         $_ = "*" for $outputList[3];
 | |
|     }
 | |
|     elsif ( $type eq "track" ) {
 | |
|         $_ = "*" for $outputList[4];
 | |
|     }
 | |
| 
 | |
|     # easy to read representation for zero
 | |
|     for (@outputList) {
 | |
|         defined $_ or $_ = '.';
 | |
|     }
 | |
| 
 | |
|     write;
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------
 |