#! perl -w
#==============================================================================
# @(#) $Id$
#------------------------------------------------------------------------------
##
## Name:
##
## cfx5parhosts
##
## Purpose:
##
## Update and modify the ANSYS CFX parallel host list information.
##
## Synopsis:
#S
#S cfx5parhosts [-user|-system|-file <file>] 
#S              [-update] [-add <host>[,<host>[,...]]] [-merge <file>]
#S
## []  denotes an optional argument.
## |   separates mutually exclusive arguments. 
## ... following an argument indicates that it may be repeated.
## <>  denotes that substitution of a suitable value is required. All other
##     arguments are keywords, some of which have a short form.
##
## Arguments:
##
#A -add <host>[,<host>[,...]]
##     Add information about the named host(s) to the file.  This
##     assumes that ANSYS CFX is installed in the same directory on each
##     listed host as on the host on which you are running.
##
##     <host> may be specified as [<user>@]<hostname>[:<ANSYS CFX root>]
##     if the user name or the ANSYS CFX installation root directory
##     differ from the local host.
##
##     To add a set of separately-installed Windows hosts to the
##     hostinfo.ccl file, where the installation may be in a different
##     place on each host, the recommended method is to gather the
##     hostinfo.ccl files created on each host by the installation
##     process, and merge them together using the -merge switch.
##
#A -benchmark
##     Runs a standard benchmark case, and fills in the Relative Speed
##     for the local host.  
##
#A -benchmark-parts
##     Number of partitions to use when benchmarking
##
#A -file <file>
##     Use ANSYS CFX hosts file <file>.
##
#A -merge <file>
##     Merge host information from the named file.
##
#A -no-update
##     After modifying the file, write back what information is
##     available, without attempting to fill in any missing pieces.
##
#A -strict
##     Used with -update.  Normally, hosts which exist on the network
##     but cannot be rshed to are included in the resulting file with
##     a comment.  This switch will exclude these hosts.
##
#A -system
##     Use the system host file.  This is the default.
##
#A -update
##     Updates the specified host information file.  If any hosts do
##     not have an architecture specified in the existing hostinfo.ccl
##     (for example, because it is being added via the -add switch),
##     it will connect to the host and query it to fill in the Host
##     Architecture String parameter.
##
##     Note that if the Installation Root parameter is incorrect for
##     the host, it will use the standard system commands to guess a
##     generic architecture string.  This can happen if you use -add
##     to include a host with a different installation directory than
##     the local one.
##
#A -user
##     Use the per-user host file.
##
#U ****************************************************************************
#U *                                                                          *
#U *     The following features are supported only within CFX and are not     *
#U *     shown by the -help option when the command is run elsewhere.         *
#U *                                                                          *
#U ****************************************************************************
#U
#U -install-setup
#U    Do the setup required by a new installation of CFX-5.
#U
#------------------------------------------------------------------------------

# get the standard ANSYS CFX library directory
BEGIN {
 if ($0 =~ m!^([a-z]:)?[\\/]!i) {
    # it's an absolute path, use it as-is
    (my $nd=$0) =~ s!(perllib[\\/])?[^\\/]*$!!i;
    unshift @INC, "$nd/perllib";
  } else {
    # wrap this in an eval, and if it fails, pray ... for example,
    # this can fail on Windows if we're running off a share.
    eval {
      require FindBin;
      FindBin->import();
      unshift @INC, "$FindBin::Bin/perllib";
      my $dummy=$FindBin::Bin; # kill used only once warning
    };
    warn $@ if $@;
  }
  if (defined $ENV{CFX_LOCAL_PERLLIB}) {
     unshift @INC, $ENV{CFX_LOCAL_PERLLIB};
  }

}

use CFX5::Init;
CFX5::Init::init();

use vars qw($CVSVersion);
sub BEGIN {
  $CVSVersion='@(#) $Id$\n';
}

use strict;

use CFX5::Output qw(:all);
use CFX5::Config qw(%Config export_conf query_conf append_to_libpath prepend_to_libpath);
#use CFX5::PrintConfig;

use CFX5::CmdLine qw(:util parse_cmdline);

use CFX5::PathManip qw(join_file dirname fullpath); # split_file fullpath basename dirname 
#use CFX5::FileTests;
use CFX5::FileOps qw(get_file_contents create_directory);

use CFX5::Parallel::HostFile;

use CFX5::PkgLoader qw(cfx5_require);

#use CFX5::Commands qw(do_cmd handle_rv);

#use CFX5::MonFile;
#use CFX5::DefFile;

#use CFX5::CCLDiff;
#use CFX5::CCLI;

#
# read the configuration
#
CFX5::Config::read_config();
prepend_to_libpath($Config{CFX_CGNS_LIBDIR});
#
# parse the command line
#
my ($update,$file,@add,@merge,$strict,$instsetup, $benchmark, $benchparts);

# defaults
$file = join_file($Config{CFX_CONFIG_DIR}, 'hostinfo.ccl');

my %args=(
          '-file+' => \$file,
          '-user' => sub { $file = join_file($Config{CFX_USER_CONFIG_DIR}, 'hostinfo.ccl'); },
          '-system' => sub { $file = join_file($Config{CFX_CONFIG_DIR}, 'hostinfo.ccl'); },

          # currently, all we do is add the local host
          '-install-setup' => sub { $instsetup=1; },

          '-update' => sub { $update=1; },
          '-no-update' => sub { $update=''; },

          '-strict' => \$strict,

          '-benchmark' => \$benchmark,
          '-benchmark-parts+' => \$benchparts,
          '-merge+' => \@merge,
          '-add+' => \@add,
         );
my @nonopt=parse_cmdline
  ( \@ARGV,
    'INCLUDE_STD',
    %args,
  );

if (@nonopt) {
  _error("Unexpected argument(s)\n", @nonopt, "\nwithout an associated option were found on the command line.", CFX5::Help::usage_msg_once());
}

exit_if_error();

#
# read the file or, if we're setting up a new install, create it.
#
my $fileobj;
if ($instsetup) {
  $update=1 unless defined $update;
  
  # Remove installation dummy file.
  if (-e $file) {
    unlink $file or die "Unable to delete temporary hostinfo.ccl file.";
  }
  
  # give a useful message
  _print("Setting up a default parallel host configuration");

  # ensure the config directory exists if we're running from the
  # installer
  my $dir=dirname($file);
  unless (-d $dir) {
    create_directory($dir)
      or _error("Unable to create directory $dir for host configuration file: $!");
  }
}

if (-e $file) {
  $fileobj=CFX5::Parallel::HostFile->from_file($file);
} else {
  _warn("The system default host file $file is missing, generating a default file containing the local host.")
    unless $instsetup;
  $fileobj=CFX5::Parallel::HostFile->new_default();

  # ensure the config directory exists if we're going to want to write
  # to it
  my $dir=dirname($file);
  unless (-d $dir) {
    create_directory($dir)
      or _error("Unable to create directory $dir for host configuration file: $!");
  }

  # we actually do the write now, to make sure we catch errors early,
  # in case we're benchmarking.  It's safe/quick to use
  # fill_in_and_write, since we know we're on the local system!
  $fileobj->fill_in_and_write($file);
}

exit_if_error();

#
# add any requested information
#
for my $tomerge (@merge) {
  _info("Merging host information from $tomerge");
  $fileobj->read($tomerge);
}
for my $toadd (map { split(/,\s*/,$_) } @add) {
  _info("Adding information for host $toadd");
  $fileobj->add_host($toadd);
}

exit_if_error();

use CFX5::Config::def CFX5_BENCHMARK_FILE => sub {
  join_file($Config{CFX_EG_DIR}, 'Benchmark.def');
};

#
# if asked to benchmark the local host, do so
#
if ($benchmark) {
  cfx5_require('CFX5::Simulation');
  cfx5_require('CFX5::Parallel::MPI');
  my $sim = new CFX5::Simulation();

  $sim->add_configuration(1, 0, "Default");
  $sim->createConfig(1, 0);
  $sim->singleconfig(1);
  $sim->singleconfig_deffile(fullpath($Config{CFX5_BENCHMARK_FILE}));
  $sim->initConfig(1, 0);

  exit_if_error();

  if(defined $benchparts and $benchparts > 1){
    $sim->set_config_option(0, '-par-local');
    $sim->set_config_option(0, '-part', $benchparts);
  }
  else {
    $benchparts=1;
  }

  CFX5::Parallel::MPI::export_conf();

  $sim->updateSimControlCCL() ;
  unless ($sim->Check()) {
    exit 1;
  }

  exit_if_error();

  _print("Running benchmark with $benchparts partitions. For best results, ensure that no other jobs are running on this machine while the benchmark is running.");
  unless ($sim->runConfig(1,0)) {
    exit_if_error();
    # if it failed, but didn't signal an error, we do so now.
    _error("Unable to run standard benchmark case");
    exit_if_error();
  }

  my $outfile=$sim->{configmap}{1}{1}{object}->outfile();

  my $cputime;

  my $mode = 0;
  open my $fh, "<$outfile" or _abort("Error opening ANSYS CFX solver output file", $outfile, ": $!");
  while (my $line = readline $fh) {
    if ($line =~ m/^\s+\|\s+(.+)\s+\|/) {
      $mode = ($1 =~ m/^CPU Requirements of Numerical Solution/) ? 1 : 0;
    }

    next unless ($mode);
    $cputime = $1 if ($line =~ m/^\s+Total\s+([\d\.dDeE\+-]+)/);
  }
  close ($fh);

  if (defined $cputime) {
    my $host=$fileobj->host($Config{CFX_HOST});
    # the benchmark system takes 541 seconds, roughly an Athlon 1800.
    my $speed=(sprintf("%5.2f", (541 / $cputime))) + 0;
    $host->speed($speed);
    my $pcpus=(sprintf("%8.2f", $cputime)) + 0;
    _print("Host $Config{CFX_HOST} completed the benchmark test case in $pcpus CPU seconds, and so has relative speed $speed, compared to the benchmark system.");
  } else {
    _error("Error finding the total CPU time in the ANSYS CFX solver output file", $outfile . ": perhaps the job did not complete properly.");
  }
}

exit_if_error();

#
# finally, write the new file
#
if ($update) {
  _info("Updating host information in $file");
  $fileobj->fill_in_and_write($file, $strict);
} else {
  $fileobj->write($file);
}

exit_if_error();

exit(0);
