/************************************************************
 * HMMER - Biological sequence analysis with profile HMMs
 * Copyright (C) 1992-2003 Washington University School of Medicine
 * All Rights Reserved
 * 
 *     This source code is distributed under the terms of the
 *     GNU General Public License. See the files COPYING and LICENSE
 *     for details.
 ************************************************************/

/* pvm.c
 * SRE, Wed Aug  5 15:40:09 1998 [St. Louis]
 * 
 * PVM code shared amongst pvm masters and slaves.
 * 
 * CVS $Id: pvm.c,v 1.9 2003/06/13 20:05:31 eddy Exp $
 */
#include "config.h"
#include "squidconf.h"

#ifdef HMMER_PVM
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pvm3.h>

#include "squid.h"
#include "structs.h"
#include "funcs.h"

/* Function: PVMSpawnSlaves()
 * Date:     SRE, Wed Aug 19 14:01:39 1998 [St. Louis]
 *
 * Purpose:  Spawn the slaves.
 *           We use the "speed" field for each host to
 *           determine how many tasks should be started
 *           on it. 1000 indicates a single processor;
 *           2000 indicates a dual processor; etc.
 *           Since hmmpfam-pvm load balances automatically,
 *           the relative speed of the processor(s) is 
 *           irrelevant.
 *
 * Args:     slave       - name of slave process to spawn ("hmmpfam-pvm")
 *           ret_tid     - RETURN: malloc'ed list of slave tid's.
 *           ret_nslaves - RETURN: total number of slaves.
 *
 * Returns:  (void).
 *           caller must free() ret_tid.
 */
void
PVMSpawnSlaves(char *slave, int **ret_tid, int *ret_nslaves)
{
  struct pvmhostinfo *hostp;
  int nodes;			/* total number of nodes in the VM       */
  int nslaves;			/* RETURN: total number of slaves        */
  int ntasks;			/* number of tasks to start on this node */
  int code;			/* a code returned from a PVM call */
  int *tid;                     /* array of slave task tids */
  int *dtid;                    /* array of host PVMD tids; for pvm_notify() */
  int i;

  SQD_DPRINTF1(("requesting PVM configuration...\n"));
  if (pvm_config(&nodes, NULL, &hostp) != 0) Die("PVM not responding");
  dtid = MallocOrDie(sizeof(int) * nodes);
  nslaves = 0;
  for (i = 0; i < nodes; i++)
    {
      dtid[i] = hostp[i].hi_tid;
      ntasks = hostp[i].hi_speed / 1000;
      if (ntasks == 0) continue;

      if (nslaves == 0) tid = MallocOrDie(sizeof(int) * ntasks);
      else              tid = ReallocOrDie(tid, sizeof(int) * (ntasks+nslaves));
      code = pvm_spawn(slave, NULL, PvmTaskHost, hostp[i].hi_name, ntasks, tid + nslaves);
      if (code < ntasks) {	/* Careful error diagnostics. Important! */
	pvm_exit(); 
	switch (*(tid+nslaves)) {
	case PvmBadParam:
	  Die("pvm_spawn claims PvmBadParam - code error?");
	case PvmNoHost:
	  Die("pvm_spawn: host %d (%s): not in virtual machine", 
	      i, hostp[i].hi_name);
	case PvmNoFile:
	  Die("pvm_spawn: host %d (%s): %s not in path",
	      i+1, hostp[i].hi_name, slave);
	case PvmNoMem:
	  Die("pvm_spawn claims that host %s has insufficient memory",
	      hostp[i].hi_name);
	case PvmSysErr:
	  Die("pvm_spawn: host %d (%s): pvmd not responding",
	      i+1, hostp[i].hi_name);
	case PvmOutOfRes:
	  Die("pvm_spawn claims it is out of resources.");
	default:
	  Die("Spawned too few slaves on node %s; expected %d got %d\n", hostp[i].hi_name, ntasks, code); 
	}
      }
      nslaves += ntasks;
      SQD_DPRINTF1(("Spawned %d slaves on host %s...\n", ntasks, hostp[i].hi_name));
    }
  if (nslaves == 0) { pvm_exit(); Die("No slaves were spawned"); }

  /* Arrange to be notified in case of trouble
   */
  if (pvm_notify(PvmTaskExit, HMMPVM_TASK_TROUBLE, nslaves, tid) != 0)
    { pvm_exit(); Die("pvm_notify() unexpectedly failed"); }
  if (pvm_notify(PvmHostDelete, HMMPVM_HOST_TROUBLE, nodes, dtid) != 0)
    { pvm_exit(); Die("pvm_notify() unexpectedly failed"); }

  *ret_tid     = tid;
  *ret_nslaves = nslaves;
  free(dtid);
  return;
}

/* Function: PVMConfirmSlaves()
 * Date:     SRE, Mon Oct 26 17:31:42 1998 [St. Louis]
 *
 * Purpose:  Make sure all the slaves initialized properly;
 *           after the master spawns and initializes them,
 *           they're supposed to send back a code. Valid 
 *           codes are in structs.h and include:
 *              HMMPVM_OK          everything's fine
 *              HMMPVM_NO_HMMFILE  file not found (hmmpfam)
 *              HMMPVM_NO_INDEX    no SSI file found (hmmpfam)
 *              HMMPVM_BAD_INIT    miscellaneous error
 *           They also send back the RELEASE code, which
 *           must match the master. This was added as an
 *           integrity check for bug#1.
 *              
 * Args:     slave_tid     array of nslaves TIDs
 *           nslaves       number of slaves
 *
 * Returns:  (void)
 *           If everything isn't OK, we Die() here.
 */
void
PVMConfirmSlaves(int *slave_tid, int nslaves)
{
  int i;
  struct timeval tmout;
  int code;			/* code returned by slave */
  int bufid;
  char *slaverelease;

  tmout.tv_sec  = 5;		/* wait 5 sec before giving up on a slave. */
  tmout.tv_usec = 0;

  SQD_DPRINTF1(("Slaves, count off!\n"));
  for (i = 0; i < nslaves; i++)
    {
      /* Do a timeout receive. If we don't hear back pronto
       * from our slaves, we've got a problem.
       */
      if ((bufid = pvm_trecv(slave_tid[i], HMMPVM_RESULTS, &tmout)) <= 0) 
	{ 
	  SQD_DPRINTF1(("Slave %d shows no response: bufid %d.\n", i, bufid));
	  PVMKillSlaves(slave_tid, nslaves); 
	  pvm_exit(); 
	  Die("One or more slaves started but died before initializing.");
	}
	
      SQD_DPRINTF1(("Slave %d: present, sir!\n", i));
      pvm_upkint(&code, 1, 1);
      slaverelease = PVMUnpackString();

      if (code != HMMPVM_OK)
	{
	  PVMKillSlaves(slave_tid, nslaves);
	  pvm_exit();
	  switch (code) {
	  case HMMPVM_NO_HMMFILE: 
	    Die("One or more PVM slaves couldn't open hmm file. Check installation.");
	  case HMMPVM_NO_INDEX:
	    Die("One or more PVM slaves couldn't open SSI index for hmm file. Check installation.");
	  case HMMPVM_BAD_INIT:
	    Die("One or more PVM slaves reports a failure to initialize.");
	  default:
	    Die("Unknown error code. A slave is confused.");
	  }
	}

      if (strcmp(slaverelease, PACKAGE_VERSION) != 0)
	{
	  PVMKillSlaves(slave_tid, nslaves);
	  pvm_exit();
	  Die("Slave %d reports that it's running release %s, which doesn't match the master (%s)", i, slaverelease, PACKAGE_VERSION);
	}
    }
}



/* Function: PVMCheckSlaves()
 * Date:     SRE, Fri Aug 14 09:04:25 1998 [St. Louis]
 *
 * Purpose:  Make sure all the slaves are alive. If they
 *           aren't, kill the rest, and die.
 *
 * Args:     slave_tid  - array of slave TIDs
 *           nslaves    - number of slaves             
 *
 * Returns:  void
 */
void 
PVMCheckSlaves(int *slave_tid, int nslaves)
{
  int trouble;			/* non-zero if a trouble message is waiting */

  trouble = pvm_nrecv(-1, HMMPVM_TASK_TROUBLE);
  if (trouble > 0)
    {
      PVMKillSlaves(slave_tid, nslaves);
      pvm_exit(); Die("One or more slave tasks exited prematurely. Shutting down.");
    }
  trouble = pvm_nrecv(-1, HMMPVM_HOST_TROUBLE);
  if (trouble > 0)
    {
      PVMKillSlaves(slave_tid, nslaves);
      pvm_exit(); Die("One or more hosts left the PVM unexpectedly. Shutting down.");
    }
}

/* Function: PVMKillSlaves()
 * Date:     SRE, Thu Aug 13 16:27:40 1998 [St. Louis]
 *
 * Purpose:  shut down the slaves, after a fatal error.
 *
 * Args:     slave_tid - array of slave tids
 *           nslaves   - number of slaves            
 *
 * Returns:  void
 */
void
PVMKillSlaves(int *slave_tid, int nslaves)
{
  int i;
  
  for (i = 0; i < nslaves; i++)
    if (pvm_kill(slave_tid[i]) != 0)
      Warn("a slave refuses to die. how annoying");
  return;
}


/* Function: PVMPackString()
 * Date:     SRE, Tue Aug 18 14:08:05 1998 [St. Louis]
 *
 * Purpose:  pack a variable length string for sending over PVM,
 *           sending its length first so the receiver can
 *           malloc appropriately.
 *
 * Args:     s - the string to send
 *
 * Returns:  1 on success. 0 on failure.
 */
int
PVMPackString(char *s)
{
  int len;
  
  len = (s == NULL) ? -1 : strlen(s);
  if (pvm_pkint(&len, 1, 1) != 0) return 0;
  if (len >= 0)
    if (pvm_pkstr(s) != 0)          return 0;
  return 1;
}

/* Function: PVMUnpackString()
 * Date:     SRE, Tue Aug 18 14:11:04 1998 [St. Louis]
 *
 * Purpose:  unpack a string.
 *
 * Args:     (void)
 *
 * Returns:  ptr to string.
 */
char *
PVMUnpackString(void)
{
  int len;
  char *s;
  
  if (pvm_upkint(&len, 1, 1) != 0) return NULL;
  if (len == -1) return NULL;

  s = MallocOrDie(sizeof(char) * (len+1));
  if (pvm_upkstr(s) != 0)          return NULL;
  return s;
}


/* Function: PVMPackTrace()
 * Date:     SRE, Wed Aug  5 15:41:36 1998 [St. Louis]
 *
 * Purpose:  Pack a trace structure for a PVM send. 
 *           The caller is responsible for calling pvm_initsend() before,
 *           and pvm_send() after packing.
 *
 * Args:     tr  - the trace structure to pack.
 *
 * Returns:  1 on success, 0 on failure.
 */
int
PVMPackTrace(struct p7trace_s *tr)
{
  if (pvm_pkint(&(tr->tlen),           1, 1) < 0) return 0;
  if (pvm_pkbyte(tr->statetype, tr->tlen, 1) < 0) return 0; 
  if (pvm_pkint(tr->nodeidx,    tr->tlen, 1) < 0) return 0;
  if (pvm_pkint(tr->pos,        tr->tlen, 1) < 0) return 0;
  return 1;
}

/* Function: PVMUnpackTrace()
 * Date:     SRE, Wed Aug  5 15:51:03 1998 [St. Louis]
 *
 * Purpose:  Unpack a trace structure from a PVM send.
 *           Caller is responsible for calling for a pvm_recv()
 *           before calling this.
 *
 * Args:     none.
 *
 * Returns:  ptr to alloc'ed trace, or NULL on failure.
 *           caller free's returned trace with P7FreeTrace().
 */
struct p7trace_s *
PVMUnpackTrace(void)
{
  struct p7trace_s *tr;
  int tlen;

  pvm_upkint(&tlen, 1, 1);
  P7AllocTrace(tlen, &tr);
  if (pvm_upkbyte(tr->statetype, tlen, 1) < 0) { P7FreeTrace(tr); return NULL;}
  if (pvm_upkint(tr->nodeidx,    tlen, 1) < 0) { P7FreeTrace(tr); return NULL;}
  if (pvm_upkint(tr->pos,        tlen, 1) < 0) { P7FreeTrace(tr); return NULL;}
  tr->tlen = tlen;
  return tr;
}


/* Function: PVMPackHMM()
 * Date:     SRE, Tue Aug 18 11:47:44 1998 [St. Louis]
 *
 * Purpose:  Pack an HMM for sending over PVM.
 *
 * Args:     hmm - the HMM to send.
 *
 * Returns:  1 on success, 0 on failure
 */
int
PVMPackHMM(struct plan7_s *hmm)
{
  int k;
  int sendflags;		/* HMM flags to send */

  sendflags = hmm->flags;
  sendflags &= ~PLAN7_HASBITS;	/* no log odds scores sent */
  sendflags &= ~PLAN7_HASDNA;	/* no DNA scores sent */

  if (pvm_pkint(&(hmm->M), 1, 1) != 0)  return 0;
  if (pvm_pkint(&sendflags, 1, 1) != 0) return 0;
  if (! PVMPackString(hmm->name))    return 0;
  if (hmm->flags & PLAN7_DESC) { if (!PVMPackString(hmm->desc)) return 0; }
  if (hmm->flags & PLAN7_RF)   { if (!PVMPackString(hmm->rf))   return 0; }
  if (hmm->flags & PLAN7_CS)   { if (!PVMPackString(hmm->cs))   return 0; }
  if (! PVMPackString(hmm->comlog))  return 0;
  if (pvm_pkint(&(hmm->nseq), 1, 1) != 0) return 0;
  if (!PVMPackString(hmm->ctime)) return 0;
  if (hmm->flags & PLAN7_MAP) { if (pvm_pkint(hmm->map, hmm->M+1, 1) != 0) return 0; }
  if (pvm_pkint(&(hmm->checksum), 1, 1) != 0) return 0;
  
  for (k = 1; k < hmm->M; k++)
    if (pvm_pkfloat(hmm->t[k], 7, 1) != 0) return 0;
  for (k = 1; k <= hmm->M; k++)
    if (pvm_pkfloat(hmm->mat[k], Alphabet_size, 1) != 0) return 0;
  for (k = 1; k < hmm->M; k++)
    if (pvm_pkfloat(hmm->ins[k], Alphabet_size, 1) != 0) return 0;
  if (pvm_pkfloat(&(hmm->tbd1), 1, 1) != 0) return 0;
  for (k = 0; k < 4; k++)
    if (pvm_pkfloat(hmm->xt[k], 2, 1) != 0) return 0;
  if (pvm_pkfloat(hmm->begin, hmm->M+1, 1) != 0) return 0;
  if (pvm_pkfloat(hmm->end,   hmm->M+1, 1) != 0) return 0;
  if (pvm_pkfloat(hmm->null,  Alphabet_size, 1) != 0) return 0;
  if (pvm_pkfloat(&(hmm->p1), 1, 1) != 0) return 0;
  if (hmm->flags & PLAN7_STATS) 
    {
      if (pvm_pkfloat(&(hmm->mu), 1, 1) != 0) return 0;
      if (pvm_pkfloat(&(hmm->lambda), 1, 1) != 0) return 0;
    }
  return 1;
}


/* Function: PVMUnpackHMM()
 * Date:     SRE, Tue Aug 18 13:56:13 1998 [St. Louis]
 *
 * Purpose:  Unpack an HMM from PVM.
 *
 * Args:     (void)
 *
 * Returns:  ptr to HMM, or NULL
 */
struct plan7_s *
PVMUnpackHMM(void)
{
  struct plan7_s *hmm;
  int k;
  int M;

  if (pvm_upkint(&(M), 1, 1) != 0) return NULL;
  hmm = AllocPlan7(M);

  if (pvm_upkint(&(hmm->flags), 1, 1) != 0)  return NULL;
  if ((hmm->name = PVMUnpackString()) == NULL) return NULL;
  if (hmm->flags & PLAN7_DESC) { if ((hmm->desc = PVMUnpackString()) == NULL) return NULL; }
  if (hmm->flags & PLAN7_RF)   { if ((hmm->rf   = PVMUnpackString()) == NULL) return NULL; }
  if (hmm->flags & PLAN7_CS)   { if ((hmm->cs   = PVMUnpackString()) == NULL) return NULL; }

  if ((hmm->comlog = PVMUnpackString()) == NULL)  return NULL;
  if (pvm_upkint(&(hmm->nseq), 1, 1) != 0) return NULL;
  if ((hmm->ctime = PVMUnpackString()) == NULL) return NULL;
  if (hmm->flags & PLAN7_MAP) { if (pvm_upkint(hmm->map, hmm->M+1, 1) != 0) return NULL; }
  if (pvm_upkint(&(hmm->checksum), 1, 1) != 0) return NULL;

  for (k = 1; k < hmm->M; k++)
    if (pvm_upkfloat(hmm->t[k], 7, 1) != 0) return NULL;
  for (k = 1; k <= hmm->M; k++)
    if (pvm_upkfloat(hmm->mat[k], Alphabet_size, 1) != 0) return NULL;
  for (k = 1; k < hmm->M; k++)
    if (pvm_upkfloat(hmm->ins[k], Alphabet_size, 1) != 0) return NULL;
  if (pvm_upkfloat(&(hmm->tbd1), 1, 1) != 0) return NULL;
  for (k = 0; k < 4; k++)
    if (pvm_upkfloat(hmm->xt[k], 2, 1) != 0) return NULL;
  if (pvm_upkfloat(hmm->begin, hmm->M+1, 1) != 0) return NULL;
  if (pvm_upkfloat(hmm->end,   hmm->M+1, 1) != 0) return NULL;
  if (pvm_upkfloat(hmm->null,  Alphabet_size, 1) != 0) return NULL;
  if (pvm_upkfloat(&(hmm->p1), 1, 1) != 0) return NULL;
  if (hmm->flags & PLAN7_STATS) 
    {
      if (pvm_upkfloat(&(hmm->mu), 1, 1) != 0) return NULL;
      if (pvm_upkfloat(&(hmm->lambda), 1, 1) != 0) return NULL;
    }
  return hmm;
}


#endif /* HMMER_PVM */
