/*
 * loaddaemon/loaddaemon.c
 * 
 * loadavgd.c -- load average daemon for AIX 3.1
 * Copyright 1991 Jussi M{ki. All Rights reserved. 
 * NON-COMMERCIAL USE ALLOWED. YOU MAY USE, COPY AND DISTRIBUTE 
 * THIS PROGRAM FREELY AS LONG AS ORIGINAL COPYRIGHTS ARE LEFT
 * AS THEY ARE.
 *
 */

/*
 * This program gets the load information and passes it along to the
 * NQS scheduler node.  The information currently passed with
 * version 1 of the message is the number of NQS jobs running and
 * the 1, 5, and 15 minute load averages.
 */

#undef  HAVE_MONITOR
#if  IS_SGI | IS_HPUX | IS_IBMRS | IS_DECOSF | IS_BSD | IS_SOLARIS | IS_LINUX
#define HAVE_MONITOR 1
#endif

#include <libsal/debug/internal.h>

#include <libnqs/license.h>
#include <libnqs/proto.h>
#include <libnqs/nqsvars.h>             /* NQS global vars */
#include <libnqs/nqspacket.h>           /* NQS packet types */
#include <libnqs/netpacket.h>           /* NQS network packet types */
#include <SETUP/autoconf.h>
#include <libsal/license.h>
#include <libsal/proto.h>
#include <libsal/defines.h>

#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#if	IS_SGI
#include <sys/sysinfo.h>
#include <sys/syssgi.h>
#include <sys/sysmp.h>
#include <sys/proc.h>
#include <sys/var.h>
#include <sys/file.h>
#endif

#if	IS_IBMRS
#include <sys/sysinfo.h>
#include <nlist.h>
/*
 * These two must be coordinated with the monitor package!!!
 */
#ifndef LOADAVGD_PORT
#define LOADAVGD_PORT 2112
#endif
#ifndef LOADAVGD_LOCATION
#define LOADAVGD_LOCATION "/usr/local/bin/loadavgd"
#endif
#endif

#if	IS_HPUX | IS_SOLARIS 
#include <sys/param.h>
#include <sys/sysinfo.h>
#include <nlist.h>
#endif

#if	IS_ULTRIX
#include <sys/sysinfo.h>
#endif

#if	IS_DECOSF | IS_BSD
#include <nlist.h>
#endif

#if IS_SOLARIS
#include <kvm.h>
#endif

#if IS_LINUX
#include <linux/kernel.h>
#include <linux/sys.h>
int sysinfo(struct sysinfo *);
#endif

static void calcloadavg ( double *load );
static int get_req_count ( void );
#if IS_IBMRS
static void handle_loadavg ( void );
static loadavg_init ( int observ[1] );
static loadavg_put ( int observ[1] );
static void load_abort ( void );
static update_loadavg ( int observ[1] );
#endif
static void load_daeshutdown ( int );
static void reload ( int );
static void send_load ( void );
static void server_abort ( void );

/*
 * This defines the protocol level.  If the messages that pass from the
 * load daemon to the net daemon change,  then this must change.
 */
#define LOAD_VERSION 1
/* 
 * We can read the SGI values from the kernel and don't need
 * to keep track on our own.  For others (i.e. IBM without monitor) we need to
 * keep the information here.
 * 
 * update load-values after this time 
 */
#define LOAD_UPDATE_SECS 60

#if IS_IBMRS
/* ring buffers for runque and runocc values */
#define SIZE_STORE ((60/LOAD_UPDATE_SECS)*5)
static int data[3][SIZE_STORE];
static int sum[3];
/* ring buffer index values */
static int rbi_current = -1;
static int rbi_tail = SIZE_STORE-(5*60)/LOAD_UPDATE_SECS;
#endif

/*** main
 *
 *
 *      int main():
 *      Start of the NQS load daemon.
 */
int main(int argc, char *argv[])
{
    char    *root_dir;

    /* initialise the debugging subsystem.  As we don't have a debug
     * level yet, default to 1 */
    sal_debug_Init(1, "NQS loaddaemon", server_abort);
  
    /* at this point, file descriptor #4 should be our line out to
     * syslogd */
    
    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_INFO, "Loaddaemon version %s starting up.\n",  NQS_VERSION);
    /*
     *  Set the name of this process to a more appropriate string.
     *
     *  We use sprintf() to pad with spaces rather than null
     *  characters so that slimy programs like ps(1) which
     *  walk the stack will not get confused.
     */
    Argv0 = argv[0];		/* Save pointer to daemon argv[0] */
    Argv0size = strlen (Argv0);       /* and size for later use */
#if     TEST
    sprintf (Argv0, "%-*s", Argv0size, "xNQS loaddaemon");
#else
    sprintf (Argv0, "%-*s", Argv0size, "NQS loaddaemon");
#endif
  
    reload(0);

    /*
     *  Disable certain signals.
     */
    signal (SIGQUIT, SIG_IGN);
    signal (SIGPIPE, SIG_IGN);
    signal (SIGINT,  SIG_IGN);
#if	IS_POSIX_1 | IS_SYSVr4
    signal (SIGUSR1, SIG_IGN);
    signal (SIGUSR2, SIG_IGN);
    signal (SIGCLD, SIG_IGN); 
#else  
#if     IS_BSD
    signal (SIGCHLD, SIG_IGN);
#else
BAD SYSTEM TYPE
#endif
#endif
    signal (SIGTERM, load_daeshutdown); /* Shutdown on receipt of SIGTERM */ 
    /*
     *  All files are to be created with the stated permissions.
     *  No additional permission bits are to be turned off.
     */
    umask (0);
    /*
     *  Set up the environment variables necessary for directory
     *  independent installation.  If these variables do not exist,
     *  NQS will use the compiled defaults.  The $NQS_HOME/rnqs.config
     *  file is read to create environment variables that define where
     *  major NQS directories reside.  Major directories are as
     *  follows:
     *    NQS_LIBEXE - NQS daemon programs and qmgr help file.
     *    NQS_LIBUSR - NQS user interface programs.
     *    NQS_MAN - NQS man pages directory.
     *    NQS_NMAP - NQS network map directory.
     *    NQS_SPOOL - NQS spooling directories.
     */
    if ( ! buildenv()) {
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon unable to establish directory independent environment.\n");
    }
    root_dir = getfilnam (Nqs_root, SPOOLDIR);
    if (root_dir == (char *)NULL) {
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon unable to determine root directory name.\n");
    }
    /*
     *  Change directory to the NQS "root" directory.
     */
    if (chdir (root_dir) == -1) {
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: Unable to chdir to %s.\n", Nqs_root);
    }
    relfilnam (root_dir);
    /*
     *  Determine the machine-id of the local host.
     */
    if (localmid (&Locmid) != 0) {
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: unable to determine machine-id of local host.\n");
    }

    while (1) {
	sleep(LOAD_UPDATE_SECS);
	send_load();
    }
}

static void
send_load()
{
    static char output_buf[BUFSIZ];
    double loadv[3];
    int i;
    int sd;                          /* Socket descriptor */
    short timeout;                   /* Seconds between tries */
    long transactcc;                 /* Holds establish() return value */
    time_t time_now;
    int	nqs_jobs;		     /* Number of jobs currently running */
    static time_t last_report_time = 0;


    time(&time_now);
    if (last_report_time + Defloadint > time_now) return;
    last_report_time = time_now;
    loadv[0] = 9999.;
    loadv[1] = 9999.;
    loadv[2] = 9999.;
    calcloadavg(loadv);
    sprintf(output_buf,"%f %f %f", loadv[0],loadv[1],loadv[2]);
    nqs_jobs = get_req_count();
    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "Loaddaemon: %s jobs %d.\n", output_buf,  nqs_jobs);
    interclear();
    interw32i(LOAD_VERSION);
    interw32i(nqs_jobs);
    interwstr (output_buf);

    sd = establish (NPK_LOAD, LB_Scheduler, 0,  "root", &transactcc);
    tcmmsgs (transactcc, SAL_DEBUG_MSG_DEBUGMEDIUM, "loaddaemon: establish ");
    
    if (sd == -2) {
        /*
         * Retry is in order.
         */
        timeout = 1;
        do {
            nqssleep (timeout);
            interclear ();
            interw32i (LOAD_VERSION);
            interw32i(nqs_jobs);
            interwstr (output_buf);
            sd = establish (NPK_LOAD, LB_Scheduler, 0, "root", &transactcc);
            timeout *= 2;
        } while (sd == -2 && timeout <= 16);
    }
    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM,  "loaddaemon: closing socket %d\n", sd);
    if ( sd > 0 ) {
	i = close (sd);			/* We don't need it any more */
	if (i) {
	    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_WARNING,  "loaddaemon: error closing socket, errno %d\n", errno);
	}
    }
}

/*
 * Get the count of requests on the system.
 * Returns either -1 for a failure,  or 0 or positive number for count
 * of jobs on the system.
 */
static int get_req_count()
{
    struct confd *queuefile;            /* MUST be the NQS queue file */
    struct gendescr *descr;
    int req_count;
    
    if ((queuefile = opendb (Nqs_queues, O_RDONLY)) == NULL) {
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_WARNING, "Loadaemon: Unable to open the NQS queue database, errno: %d\n",  errno);
	return (-1);
    }
    descr = nextdb (queuefile);

    req_count = 0;
    while (descr != (struct gendescr *)0) {
	req_count += descr->v.que.runcount + descr->v.que.arrivecount;
        descr = nextdb (queuefile);  /* Get the next queue */
    }
    closedb (queuefile);
    return(req_count);
}


/*** server_abort
 *
 *
 *      void server_abort():
 *
 *      Abort Network server execution, writing a message to
 *      the NQS log process.
 */

static void server_abort (void)
{
        if (errno) 
           sal_debug_dprintf (SAL_DEBUG_MSG_ERRORAUTHOR, "%s.\n", asciierrno());
        sal_debug_dprintf (SAL_DEBUG_MSG_ERRORAUTHOR, "Loaddaemon aborting.\n");
        exit (1);
}

/*** reload
 *
 *
 *      void reload ():
 *      When the local daemon updates a parameter,
 *      it will send a SIGHUP to the load daemon.  The load daemon
 *      should then update its copy of the parameters off the disk.
 *
 */
static void reload ( int unused )
{
        signal (SIGHUP, reload);
	sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "Loaddaemon: reloading parameters.\n");
        /*
         *  Initialize the in-core copy of the parameters.
         *  The NQS local daemon uses SIGINT to let us know
         *  when the parameters have changed. 
         */
        if ((Paramfile = opendb (Nqs_params, O_RDONLY)) == NULL) {
                sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "Loaddaemon: unable to open general parameters file.\n");
        }
	/*
	 * Get the Scheduler mid and debug information.
	 */
        seekdb (Paramfile, 0);
        ldparam ();
	closedb(Paramfile);
	sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "Loaddaemon: Defloadint now %d.\n", Defloadint);
}

/*** load_daeshutdown
 *
 *
 *      void net_daeshutdown():
 *      Catch SIGTERM signal to shutdown.
 */
static void load_daeshutdown( int unused )
{
        signal (SIGTERM, SIG_IGN);
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_WARNING, "Loaddaemon shutting down.\n");
        exit (0);
}

#if IS_SGI
static
void calcloadavg(double load[3])
{
    unsigned long	kernaddr;
    static int kmem = -1;
    long avenrun[3];
    int	i, j;	
    
    kernaddr = sysmp(MP_KERNADDR, MPKA_AVENRUN) & 0x7fffffff;

    if (kmem == -1) { 
        if( (kmem = open("/dev/kmem", 0) ) == -1) {
            sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "Loaddaemon: kmem open error %d\n", errno);
        }
    }
    if( lseek(kmem, (off_t)kernaddr, 0) == -1 ){
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem seek error %d\nkmem = %d,  kernaddr = %x\n", errno, kmem,  kernaddr);
    }
    i = read(kmem, (char *)avenrun, sizeof(avenrun));
    if( i != sizeof(avenrun) ){
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORAUTHOR, "Loaddaemon: kmem read error %d\nLoaddaemon: read %d\n",  errno, i);
    }
    for (i = 0; i < 3; i++)
            load[i] = (double) avenrun[i] / 1024.;
}
#endif  /* SGI */

#if IS_IBMRS

/* getloadavg.c -- routine to get loadavgd-values
** this version is used with loadavgd-server in aix3
*/


#ifndef LOADAVGD_PORT
#define LOADAVGD_PORT 2112
#endif
#ifndef LOADAVGD_LOCATION
#define LOADAVGD_LOCATION "/usr/local/bin/loadavgd"
#endif

static void signal_handler(int sig)
{
    /* set this signal-handler again */
    signal(sig,signal_handler);
}

static void calcloadavg(double loadv[])
{
    static int initted=0;
    static int sock;
    static struct sockaddr_in server;
    static struct hostent *hp;
    static int alarm_sig;
    char buf[80];
    int status;
    int server_len = sizeof(struct sockaddr_in);
    int errno2;
    int i;

    if (! initted) {
	initted=1;
        sock = socket(AF_INET, SOCK_DGRAM, 0);
      	ENSURE_RESOURCE(sock < 0, "datagram socket");
	server.sin_family = AF_INET;
	hp = gethostbyname("localhost");
	if (hp == 0) {
	    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA,"loadaemon: localhost unknown\n");
	}
	bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
	server.sin_port = htons(LOADAVGD_PORT);
    }
    
    /* send something to loadavgd-server to get reply back */
    status = sendto(sock,"\n\000",2,0, (struct sockaddr *)&server,sizeof(server));
    if (status == -1) {
        sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "loadaemon: getloadavg: sendto loadavgd\n");
    }    
    signal(SIGALRM,signal_handler);
    alarm(2); /* lets wait for 2 second to get respond from server */
    status = recvfrom(sock, buf, 80,0, (struct sockaddr *)&server, &server_len);
    errno2 = errno;
    alarm(0);
    signal(SIGALRM,SIG_DFL);

    if (status==-1) {
	if (errno2 == EINTR) { /* if recvfrom was interrupted by alarm */
	    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "***getloadavg: starting loadavgd-daemon ***\n");
	    launch_loadavgd();
	    sleep(1);
	    status = sendto(sock,"\n\000",2,0, (struct sockaddr *)&server,sizeof(server));
	    if (status == -1) {
	        sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "getloadavg: sendto loadavgd\n");
	    }
	    status = recvfrom(sock, buf, 80,0, (struct sockaddr *)&server, &server_len);
	}
	if (status == -1) {
	    sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "getloadavg: recvfrom from loadavgd daemon\n");
	}
    }
    sscanf(buf,"%lf %lf %lf",&loadv[0],&loadv[1],&loadv[2]);
}


/* launch_loadavgd.c -- start loadavgd process 
** Called from getloadavg if loadavgd server is not running
** or accepting connections to port.
**
*/

launch_loadavgd()
{
    char buf[32];
    int i;
    
    sprintf(buf,"%d",LOADAVGD_PORT);
    switch (fork()) {
      case 0: /* child */
	execl(LOADAVGD_LOCATION,"loadavgd",buf,0);
	sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "getloadavg: cannot exec %s\n", LOADAVGD_LOCATION);
	/* never returns */
      case -1: /* error */
	sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "getloadavg: cannot fork \n");
      default: /*parent */
	break;
    }
}
#endif  /* IBMRS */

#if	IS_HPUX
struct  nlist nl[] = {
#if	IS_HPUX10
# define unixpath "/stand/vmunix"
#else
# define unixpath "/hp-ux"
#endif
#ifdef __hppa       /* series 700 & 800 */
        { "avenrun" },
#else               /* series 300 & 400 */
        { "_avenrun" },
#endif
        { 0 },
};
static
void calcloadavg(double load[3])
{
    static int kmem = -1;
    double avenrun[3];
    int	i;
    
    if(kmem == -1) {
        nlist(unixpath, nl);
        if (nl[0].n_type==0) {
            sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: namelist error\n");
        }
        if((kmem = open("/dev/kmem", 0)) == -1) {
            sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "Loaddaemon: kmem open error %d\n", errno);
        }
    }
    if( lseek(kmem, (off_t)nl[0].n_value, 0) == -1 ){
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem seek error %d\nloaddaemon: kmem = %d,  kernaddr = %x\n", errno, kmem, nl[0].n_value);
    }
    if( read(kmem, (char *)avenrun, sizeof(avenrun)) != sizeof(avenrun) ){
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem read error %d\nLoaddaemon: read %d\n", errno, i);
    }
    for (i = 0; i < 3; i++)
        load[i] = avenrun[i];

}
#endif  /* HPUX */
#if   IS_DECOSF | IS_BSD
struct  nlist nl[] = {
#define unixpath "/vmunix"
        { "_avenrun" },
        { 0 },
};

static
void calcloadavg(double load[3])
{
    static int kmem = -1;
    long avenrun[3];
    int       i;

    if(kmem == -1) {
        nlist(unixpath, nl);
        if (nl[0].n_type==0) {
          sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: namelist error\n");
        }
	if((kmem = open("/dev/kmem", 0)) == -1) {
	    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem open error %d\nloaddaemon: kmem = %d,  kernaddr = %x\n", errno, kmem, nl[0].n_value);
	}
    }
    if ( lseek (kmem, (off_t) nl[0].n_value, 0) == -1) {
	sal_dprintf(SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem seek error %d\n", errno);
    }
    if( read(kmem, (char *)avenrun, sizeof(avenrun)) != sizeof(avenrun) ){
        sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem read error %d\nLoaddaemon: read %d\n", errno, i);
    }
    for (i = 0; i < 3; i++)
        load[i] = ((double) avenrun[i]) / (double) 1024.;
}
#endif   /* DECOSF or Sun (BSD43) */

#if IS_SOLARIS
struct  nlist nl[] = {
        { "avenrun" },
        { 0 },
};
#define unixpath "/kernel/unix"
static
void calcloadavg(double load[3])
{
    static int kmem = -1;
    long avenrun[3];
    int	i;
    
    /*
     * Solaris > 2.5 doesn't have a kernel file.  Use the kvm_* interface
     * to read the kernel name list.
     */
    kvm_t *kvm_handle = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
    if (kvm_handle == NULL || kvm_nlist(kvm_handle, nl) != 0)
    {
	sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kernel open error\n");
    }
    if((i = kvm_read(kvm_handle, nl[0].n_value, (void *)avenrun, sizeof(avenrun))) != sizeof(avenrun) )
    {
	sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORDATA, "Loaddaemon: kmem read error %d\nLoaddaemon: read %d\n", errno, i);
    }
    kvm_close(kvm_handle);
    
    for (i = 0; i < 3; i++)
	load[i] = ((double)avenrun[i]) / 1024.0;
}
#endif /* SOLARIS */

#if  IS_LINUX
static
void calcloadavg(double load [3])
{
    struct sysinfo info;
    int i;
    if( sysinfo(&info) == -1) {
	sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE,
		     "Loaddaemon: error calling  sysinfo %d\n", errno);
    }

    sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_DEBUGMEDIUM, "Loaddaemon: (%d,%d,%d)\n",
		 info.loads[0],info.loads[1],info.loads[2]);

    /* Dividing by 1024 only seems to leave the numbers too big. */
    for (i = 0; i < 3; i++)
	load[i] = (double) info.loads[i] / 1024. / 100. ;
}
#endif /* LINUX */

/*
 * The following code appears to be an old implementation of loaddaemon
 * for the IBMRS platform.  It has been left for reference purposes
 * but commented out using a undefined variable.
 */
#if 	IS_IBMRS_UNDEFINED
static
void calcloadavg(double load [3])
{
  int iparam;

  for (iparam=0; iparam<3; iparam++) {
    load[iparam] = (double ) sum[iparam] / (double) SIZE_STORE;
  }
}

static loadavg_init(int observ[3])
{
  int iobs, iparam;
  for (iparam=0;iparam<3;iparam++) {
    sum[iparam] = 0;
  }
  for (iparam=0;iparam<3;iparam++) {
    for (iobs=0;iobs<SIZE_STORE;iobs++) {
      data[iparam][SIZE_STORE-iobs-1] = observ[iparam];
      sum[iparam] += observ[iparam];
    }
  }
}

static
update_loadavg(int observ[3])
{
  static int prev_update=0;

  if (prev_update==0) loadavg_init(observ);
  if (time(0)-prev_update > LOAD_UPDATE_SECS) {
    loadavg_put(observ);
    prev_update=time(0);
  }
}

#define RB_INC(index) ((index+1>=(SIZE_STORE))?(0):(index+1))

static
loadavg_put(int observ[3])
{
  int iparam;

  rbi_current = RB_INC(rbi_current);
  rbi_tail   = RB_INC(rbi_tail);
  for (iparam = 0; iparam < 3; iparam++) {
      data[iparam][rbi_current]=observ[iparam];
      sum[iparam] += observ[iparam] - data[iparam][rbi_tail];
  }
}
static
void handle_loadavg()
{
    static int initted=0;
    static int fd;
    static struct sysinfo si;
    static struct nlist kernelnames[] = {
	{"sysinfo", 0, 0, 0, 0, 0},
	{NULL, 0, 0, 0, 0, 0},
        };

    if (!initted) {
	initted = 1;
	fd = open("/dev/kmem", O_RDONLY);
	if (fd < 0) {
            sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_ERRORRESOURCE, "Loaddaemon: kmem open error %d\n", errno);
	}
	if (knlist(kernelnames,1,sizeof(struct nlist)) == -1) {
            sal_dprintf (SAL_DEBUG_INFO, SAL_DEBUG_MSG_WARNING, "Loaddaemon: knlist entry not found\n");
	}
    }
    lseek(fd, kernelnames[0].n_value,SEEK_SET);
    read(fd,&si,sizeof(si));
    update_loadavg(si.runque,si.runocc);
}
#endif

#ifndef HAVE_MONITOR
/*
 * Code for other architectures / operating systems goes here.
 * For now just return 9999 for the load average.
 */
static
void calcloadavg(double load[3])
{
}
#endif
