/*
 * nqsd/nqs_psc.c
 * 
 * DESCRIPTION:
 *
 *	This module contains the 3 functions:
 *
 *		psc_reqcom()
 *		psc_sched()
 *		psc_spawn()
 *
 *	which control the scheduling, and spawning of NQS pipe requests.
 *	This module can be modified to implement appropriate scheduling
 *	algorithms for a particular installation as necessary.
 *
 *
 *	Original Author:
 *	-------
 *	Brent A. Kingsbury, Sterling Software Incorporated.
 *	August 12, 1985.
 */

#include <libnqs/license.h>
#include <libnqs/proto.h>

#include <libnqs/nqsxvars.h>			/* NQS global variables */

#include <libsal/license.h>
#include <libsal/proto.h>
#include <libsal/defines.h>
#include <nqsdaemon/features.h>

static int enabled_dest ( struct nqsqueue *queue );

/*** psc_reqcom
 *
 *
 *	void psc_reqcom():
 *
 *	This function is invoked whenever a running pipe request completes
 *	execution.  This function is free to spawn more pipe requests from
 *	ANY pipe queue.
 *
 *	All queues modified by the invocation of this procedure have
 *	had their corresponding NQS database queue state image updated
 *	(to match the internal queue states) upon return from this
 *	procedure.
 */
void psc_reqcom (struct request *request)
{

	/*
	 *  For the moment, we do not use any of the knowledge imparted
	 *  to us by the request that just finished execution,  Instead,
	 *  we spawn as many pipe requests as we can, within the limits
	 *  configured.
	 */
	psc_spawn();			/* Spawn all pipe requests */
}					/* within configured boundaries */


/*** psc_sched
 *
 *
 *	int psc_sched():
 *
 *	This function is invoked whenever a pipe req must be evaluated
 *	and assigned a priority by the NQS pipe req scheduling policies
 *	(which are implemented by this function).
 *
 *	The priority assigned to the req must be in the interval [0..32767].
 *	All pipe reqs with priority >= N within a given pipe queue, will,
 *	depending upon the precise scheduling criteria defined in:
 *
 *		psc_spawn()  and
 *		psc_reqcom()
 *
 *	be spawned before any pipe reqs in the same queue with a priority
 *	value < N.
 *	
 *	Returns:
 *		The assigned priority value for the specified pipe request.
 */
int psc_sched (struct rawreq *rawreq)
{
  	assert (rawreq != NULL);
  
	if (rawreq->rpriority == -1) {
	    /*
	     *  The user did not assign a priority to the req; assign
	     *  a default priority value.
	     */
	    return (MAX_RPRIORITY / 2);
	}
	return (rawreq->rpriority);	/* For now, just the intra-queue */
}

#if	ADD_NQS_DYNAMICSCHED
/*
 * Generic NQS v3.50.0 - extension of dynamic scheduling
 * 
 * Dave Safford added dynamic scheduling for batch queues in Generic
 * NQS v3.40; I've taken Dave's code and adapted it to work with pipe
 * queues and queue complexes.
 * 
 * Dynamic scheduling is achieved over a cluster by placing the load
 * balanced pipe queues on the scheduler into one (or more) queue
 * complexes.  The compare routine takes the lowest user_limit from
 * the complexes a queue belongs to, and uses that to check the status
 * of any particular queued request.
 */

void psc_resort(void)
{
  struct nqsqueue *queue;		/* Pipe queue set walking */
  struct request  *preq;
  struct request  *nextp;
  struct request  *lastp;
  int              done;
  
  for ( queue = Pripipqueset;
        queue != (struct nqsqueue *) 0;
        queue = queue->v1.pipe.nextpriority)
  {
    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "psc_resort: Starting loop for queue %s.\n", queue->q.namev.name);
    
    /* simple bubble sort - walk queue until no more changes */
    for (;;) 
    {
      done = 1;
      for (preq = queue->queuedset, lastp = (struct request *) 0;
	   preq != (struct request *) 0;
	   preq = preq->next)
      {
	if (preq->next != (struct request *) 0) 
	{
	  if (psc_compare(queue, preq, preq->next))
	  {
	    done = 0;
	    queue->q.status |= QUE_UPDATE;
	    nextp = preq->next;
	    if (lastp != (struct request *) 0)
	      lastp->next = nextp;
	    else
	      queue->queuedset = nextp;
	    preq->next = nextp->next;
	    nextp->next = preq;
	  }
	}
	lastp = preq;
      }
      if (done)
	break;
    }
    
    if (queue->q.status & QUE_UPDATE)
    {
      udb_qorder(queue);
    }
  }
}

/*
 * compare two requests and return relative priority (p2 - p1)
 * 
 * the original insertion sort simply places a new request at the
 * bottom of all other requests of equal or higher priority.
 * This comparison depreciates all "excess" requests within the same
 * priority class (where excess is defined as more requests than the
 * user_limit for the queue.)
 */

int psc_compare(struct nqsqueue *queue, struct request *p1, struct request *p2)
{
  struct request *preq;
  int             runlimit;
  int             jobcount1;
  int             jobcount2;
  int		  i;
  
  if (p1->v1.req.priority > p2->v1.req.priority)
    return 0;
  if (p1->v1.req.priority < p2->v1.req.priority)
    return 1;
  
  /* priorities are same, so check "excess" status */
  
  /* first, we check the queue complexes to determine what the
   * user limit for this queue should be.  Pipe queues do not have
   * user limits of their own (unfortunately), and so we check the
   * queue complexes the pipe queue belongs to, and take the lowest
   * userlimit we can find.
   */
  
  if (queue->v1.pipe.qcomplex[0] != (struct qcomplex *) 0)
  {
    runlimit = queue->v1.pipe.qcomplex[0]->userlimit;
  
    for (i=1; i < MAX_COMPLXSPERQ; i++)
    {	  
      if (queue->v1.pipe.qcomplex[i] != (struct qcomplex *) 0)
        if (queue->v1.pipe.qcomplex[i]->userlimit < runlimit)
          runlimit = queue->v1.pipe.qcomplex[i]->userlimit;
    }
  }
  else 
    return 0;
  
  /* get jobcount for p1 */
  jobcount1 = 1; /* count request we are checking */
  /* cound user's jobs running in queue */
  for (preq = queue->runset;
       preq != (struct request *) 0;
       preq = preq->next)
  {
    if (preq->v1.req.uid == p1->v1.req.uid)
      jobcount1++;
  }
  /* count user's jobs queue higher in queue */
  for (preq = queue->queuedset;
       (preq != (struct request *) 0) && (preq != p1);
       preq = preq->next)
  {
    if (preq->v1.req.uid == p1->v1.req.uid)
      jobcount1++;
  }
  
  /* get jobcount for p2 */
  jobcount2 = 1; /* count request we are checking */
  /* count user's jobs running in queue */
  for (preq = queue->runset;
       preq != (struct request *) 0;
       preq = preq->next)
  {
    if (preq->v1.req.uid == p2->v1.req.uid)
      jobcount2++;
  }
  /* count user's jobs queued higher in queue */
  for (preq = queue->queuedset;
       (preq != (struct request *) 0) && (preq != p1);
       preq = preq->next)
  {
    if (preq->v1.req.uid == p2->v1.req.uid)
      jobcount2++;
  }
  
  /* p1 is excess, p2 is not, so swap */
  if ((jobcount1 > runlimit) && (jobcount2 <= runlimit))
    return 1;
  else
    /* leave things as they are ... */
    return 0;
}

#endif /* ADD_NQS_DYNAMICSCHED */

/*** psc_spawn
 *
 *
 *	void psc_spawn():
 *
 *	This function is invoked whenever request activity indicates that
 *	it MAY be possible to spawn a pipe request.  It is up to the
 *	discretion of the pipe req scheduling/spawning algorithm
 *	implemented here to determine whether or not pipe req(s) should
 *	be spawned.
 *
 *	All queues modified by the invocation of this procedure have
 *	had their corresponding NQS database queue state image updated
 *	(to match the internal queue states) upon return from this
 *	procedure.
 */
void psc_spawn()
{
	register struct nqsqueue *queue;	/* Pipe queue set walking */
	register int prevpipcount;	/* Prev loop values of Gblpipcount */

	/*
	 *  Note that we are very careful to make sure that all pipe
	 *  queues with higher priorities that also have pipe requests
	 *  that can run, get to spawn first.  This becomes critical when
	 *  the number of pipe requests that can run exceeds the maximum
	 *  number of pipe requests that are allowed to simultaneously
	 *  execute.
	 */
	if (Shutdown) return;		/* Do not spawn any requests if */
					/* NQS is shutting down */
#if	ADD_NQS_DYNAMICSCHED
  	psc_resort();
#endif
  
	queue = Pripipqueset;		/* Prioritized pipe queue set */
	prevpipcount = Gblpipcount - 1;	/* Make loop go at least once */
	for ( ; queue != (struct nqsqueue *) 0 &&
	       Maxgblpiplimit > Gblpipcount && prevpipcount != Gblpipcount;
	       queue = queue->v1.pipe.nextpriority) {
	    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGHIGH, "psc_spawn: starting loop for queue %s.\n", queue->q.namev.name);
	    /*
	     *  Spawn as many requests as are allowed for this
	     *  queue.
	     */
	    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: runlimit %d runcount %d\n",  queue->q.v1.pipe.runlimit, queue->q.runcount);
	    if (queue->q.status & QUE_RUNNING) 
	    	sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: queue running\n");
	    if (queue->queuedset == (struct request *) 0) 
		sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: No requests queued\n");
	    if ( enabled_dest (queue)) 
		sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: Are enabled dests\n");
	    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: Maxgblpiplimit %d Gblpipcount %d\n", 
			Maxgblpiplimit,  Gblpipcount);
	    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGLOW, "psc_spawn: prevpipcount %d Gblpipcount %d\n", 
			prevpipcount, Gblpipcount);
            if (!(queue->q.status & QUE_RUNNING)) continue;
	    if (queue->q.v1.pipe.runlimit <=  queue->q.runcount) {
                sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "psc_spawn: Rqst not scheduled due to queue runlimit.\n");
                continue;
	    }
	    if (queue->queuedset == (struct request *) 0) {
                sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "psc_spawn: Rqst not scheduled due to none there.\n");
                continue;
	    }
	    if (!enabled_dest (queue)) {
                sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "psc_spawn: Rqst not scheduled due to no enabled dest.\n");
                continue;
	    }
	    if (Maxgblpiplimit <= Gblpipcount) {
                sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "psc_spawn: Rqst not scheduled due to Maxgblpiplimit.\n");
                continue;
	    }
	    if (prevpipcount == Gblpipcount) {
                sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "psc_spawn: Rqst not scheduled due to Gblpipcount.\n");
                continue;
	    }
	    /*
	     *  There is a pipe request that can be spawned.
	     *  Try to spawn it.
	     *
	     *  Note that the spawning of a pipe request
	     *  requires the creation of a subrequest.
	     *  Furthermore, a new network queue may have
	     *  to be created.  It is therefore possible
	     *  that the successful spawning of a pipe
	     *  request would require more memory, than
	     *  there is available.  In such a situation,
	     *  the spawn quietly fails.  By watching
	     *  Gblpipcount, this procedure can tell when
	     *  such an event has happened.
	     */
	    prevpipcount = Gblpipcount;
	    nqs_spawn (queue->queuedset, (struct device *) 0);
	    if (queue->q.status & QUE_UPDATE) {
		/*
		 *  The database image of this queue needs to
		 *  be updated.
		 */
		udb_qorder (queue);	/* Update database image */
	    }				/* and clear QUE_UPDATE */
	}
}


/*** enabled_dest
 *
 *
 *	int enabled_dest():
 *
 *	Return boolean TRUE (non-zero), if there exists an enabled
 *	destination in the destination set for the specified pipe
 *	queue.
 */
static int enabled_dest (struct nqsqueue *queue)
{
	register struct qdestmap *qdestmap;	/* Pipe destination walking */

	qdestmap = queue->v1.pipe.destset;
	while (qdestmap != (struct qdestmap *) 0) {
		if (qdestmap->pipeto->status & DEST_ENABLED) {
			/*
			 *  There exists an enabled destination for
			 *  this pipe queue.
			 */
			return (1);
		}
		qdestmap = qdestmap->next;	/* Examine next destination */
	}
	return (0);				/* No enabled destinations */
}
