
/*
 * $Header: /cvs/gnome/gperfmeter/src/data.c,v 1.15 2005/01/13 19:14:34 richb Exp $
 *
 * Copyright (c) 1990-2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>

#if defined (HAVE_NETCONFIG)
#include <sys/netconfig.h>
#include <fcntl.h>
#endif

#include <time.h>

#include "perfmeter.h"

#include <arpa/inet.h>

static void OnDeath(int);
static void RetryConnection(void);
static int *GetHostData();
static void KillChildProcess(void);

static struct timeval TIMEOUT = { 20, 0 };

PmDataStruct *PMD;


int
PmConnectToHost()
{
    enum clnt_stat clnt_stat;
    struct timeval timeout;
#if defined (HAVE_NETCONFIG)
    struct t_bind *tbind;
    struct netconfig *nconf, *netconf;
    int fd;

    KillChildProcess();

    PM->IsDead = FALSE;
    PM->IsSick = FALSE;

    netconf = (struct netconfig *) setnetconfig();
    if (netconf == NULL) {
        nc_perror("Error");
        exit(1);
    }

try_again:
    nconf = getnetconfig((void *) netconf);
    for (; nconf; ) {
        if (!strcmp(nconf->nc_proto, "udp")) {
            break;
        } else {
            nconf = getnetconfig((void *)netconf);
        }
    }

    if (nconf == NULL) {
        nc_perror("Error");
        PmWarning(PmWRN_HOSTNOTFOUND, PM->Hostname);

        PM->IsRemote = FALSE;
        endnetconfig(netconf);

        return(PmConnectToHost());
    }

    fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *) NULL);
    if (fd == -1) {
        t_error("t_open");
        endnetconfig(netconf);
        exit(1);
    }

    tbind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR);
    if (tbind == (struct t_bind *) NULL) {
        t_error("t_alloc");
        endnetconfig(netconf);
        exit(1);
    }

    if (PM->IsRemote && PM->Hostname) {
        if (rpcb_getaddr(RSTATPROG, RSTATVERS_VAR, nconf, 
                         &tbind->addr, PM->Hostname) == FALSE) {
            t_close(fd);
            goto try_again;
        }
    } else {
        if (rpcb_getaddr(RSTATPROG, RSTATVERS_VAR, nconf, 
                         &tbind->addr, PM->LocalHost) == NULL) {
            endnetconfig(netconf); 
            exit(1);
        }
    } 

    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    if ((PM->RpcVersion == PmRPC_VERSION_NONE) || 
        (PM->RpcVersion == RSTATVERS_VAR)) {

        PMD->Client = clnt_tli_create(fd, nconf, &tbind->addr, 
                                      RSTATPROG, RSTATVERS_VAR, 0, 0);

        if (PMD->Client == NULL) { 
            PmWarning(PmWRN_HOSTCONNECT, PM->Hostname);
            PM->IsDead = TRUE;
            endnetconfig(netconf);

            if (PM->IsRemote) {
                PM->IsRemote = FALSE;
                set_hostname();
                return(PmConnectToHost());
            } else {
                return(FALSE);
            }
        }
        clnt_control(PMD->Client , CLSET_TIMEOUT, (char *) &timeout);
        clnt_stat = clnt_call(PMD->Client, NULLPROC, (xdrproc_t) xdr_void, 0,
                              (xdrproc_t) xdr_void, 0, TIMEOUT);

        if (clnt_stat == RPC_SUCCESS) {
            PM->RpcVersion = RSTATVERS_VAR;
        } else if (clnt_stat != RPC_PROGVERSMISMATCH) {
            clnt_destroy(PMD->Client);
            endnetconfig(netconf);
            return(FALSE);
        } else {                                      /* Version mismatch */
            clnt_destroy(PMD->Client);
            PM->RpcVersion = RSTATVERS_TIME;
        }       
    }               

    if ((PM->RpcVersion == PmRPC_VERSION_NONE) ||
        (PM->RpcVersion == RSTATVERS_TIME)) {

        PMD->Client = clnt_tli_create(fd, nconf, &tbind->addr, 
                                      RSTATPROG, RSTATVERS_VAR, 0, 0);

        if (PMD->Client == NULL) {
            endnetconfig(netconf);
            return(FALSE);
        }

        clnt_control(PMD->Client , CLSET_TIMEOUT, (char *) &timeout);
        clnt_stat = clnt_call(PMD->Client, NULLPROC, (xdrproc_t) xdr_void, 0,
                              (xdrproc_t) xdr_void, 0, TIMEOUT);

        if (clnt_stat == RPC_SUCCESS) {
            PM->RpcVersion = RSTATVERS_TIME;
        } else {
            clnt_destroy(PMD->Client);
            endnetconfig(netconf);
            return(FALSE);
        }
    }

    endnetconfig(netconf);

#else
    struct hostent *hp;
    struct sockaddr_in serveradr;
    int snum;

    KillChildProcess();

    PM->IsDead = FALSE;
    PM->IsSick = FALSE;

    snum = RPC_ANYSOCK;
    MEMSET((char *) &serveradr, 0, sizeof(serveradr));

    if (PM->IsRemote && PM->Hostname) {
        if ((hp = gethostbyname((char *) PM->Hostname)) == NULL) {
            PmWarning(PmWRN_HOSTNOTFOUND, PM->Hostname);
            PM->IsRemote = FALSE;

            return(PmConnectToHost());
        }

        MEMCPY((char *) &serveradr.sin_addr, hp->h_addr, hp->h_length);
        endhostent();
    } else {
        if ((hp = gethostbyname(PM->LocalHost)) != NULL) {
            MEMCPY((char *) &serveradr.sin_addr, hp->h_addr, hp->h_length);
        } else {
            serveradr.sin_addr.s_addr = inet_addr(PM->LocalAddr);
        }
        endhostent();
    }

    serveradr.sin_family = AF_INET;
    serveradr.sin_port = 0;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    if ((PM->RpcVersion == PmRPC_VERSION_NONE) ||
        (PM->RpcVersion == RSTATVERS_VAR)) {
        if ((PMD->Client = clntudp_bufcreate(&serveradr, RSTATPROG,
                               RSTATVERS_VAR, timeout, &snum,
                               sizeof(struct rpc_msg), UDPMSGSIZE)) == NULL) {
            /* Could not connect to host */
            PmWarning(PmWRN_HOSTCONNECT, PM->Hostname);
            PM->IsDead = TRUE;
            if (PM->IsRemote) {
                PM->IsRemote = FALSE;
                set_hostname();
                return(PmConnectToHost());
            } else {
                return(FALSE);
            }
        }
        clnt_stat = clnt_call(PMD->Client, NULLPROC, (xdrproc_t) xdr_void, 0,
                              (xdrproc_t) xdr_void, 0, TIMEOUT);

        if (clnt_stat == RPC_SUCCESS) {
            PM->RpcVersion = RSTATVERS_VAR;
        } else if (clnt_stat != RPC_PROGVERSMISMATCH) {
            clnt_destroy(PMD->Client);
            return(FALSE);
        } else {                                      /* Version mismatch */
            clnt_destroy(PMD->Client);
            PM->RpcVersion = RSTATVERS_TIME;
        }
    }   

    if ((PM->RpcVersion == PmRPC_VERSION_NONE) ||
        (PM->RpcVersion == RSTATVERS_TIME)) {
        snum = RPC_ANYSOCK;

        if ((PMD->Client = clntudp_bufcreate(&serveradr, RSTATPROG,
            RSTATVERS_TIME, timeout, &snum, sizeof(struct rpc_msg),
            sizeof(struct rpc_msg) + sizeof(struct statstime))) == NULL) {
            return(FALSE);
        }

        clnt_stat = clnt_call(PMD->Client, NULLPROC, (xdrproc_t) xdr_void, 0,
                              (xdrproc_t) xdr_void, 0, TIMEOUT);

        if (clnt_stat == RPC_SUCCESS) {
            PM->RpcVersion = RSTATVERS_TIME;
        } else {
            clnt_destroy(PMD->Client);
            return(FALSE);
        }
    }

    if (PM->OldSocket >= 0) {
        CLOSE(PM->OldSocket);
    }

    PM->OldSocket = snum;
#endif

    if (PMD->OldClient) {
        clnt_destroy(PMD->OldClient);
    }

    PMD->OldClient = PMD->Client;

    return(TRUE);
}


/*ARGSUSED*/
static void 
OnDeath(int arg)
{
    int pid, status;

    while (waitpid(-1, &status, WNOHANG) > 0) {
        PM->ChildPID = -1;
        PM->IsRemote = 0;
        set_hostname();
        PmWarning(PmWRN_HOSTCONNECT, PM->Hostname);
        continue;
    }

    if (!PmConnectToHost()) {
        RetryConnection();
    } else {
        PM->IsDead = FALSE;
        (void) InterruptTimer(NULL);
    }
}


void
PmSetSignalHandlers()
{
    pid_t pid = getpid();

    SETPGID(pid, pid);
    SIGNAL(SIGTERM, PmSignalHandler);
    SIGNAL(SIGHUP, SIG_IGN);
}


/* Kill any background process we have started. */

/*ARGSUSED*/
void
PmSignalHandler(int pid)
{
    SIGNAL(SIGINT,SIG_IGN);
    SIGNAL(SIGCHLD,SIG_IGN);

    /* Get rid of forked processes */
    KILL(getpgrp(), SIGINT);

    exit(0);
}


static void
KillChildProcess()
{
    /* Get rid of forked processes */
    SIGNAL(SIGCHLD, SIG_IGN);
    if (PM->ChildPID > 0) {
        KILL(PM->ChildPID, SIGKILL);
        PM->ChildPID = -1;
    }
}


static void
RetryConnection()
{  
    if ((int) signal((int) SIGCHLD, OnDeath) == -1) {
        perror(PM->AppClass);
    }

    PM->ChildPID = fork();

    if (PM->ChildPID < 0) {
        /* Error forking the subprocess */
        perror(PM->AppClass);
        exit(1);
    }

    if (PM->ChildPID == 0) {
        /* In the child process */
        for (;;) {
            SLEEP(1);

            /* Keep trying to connect to the host */
            if (!PmConnectToHost()) {
                continue;
            }

            exit(3);
        }
    }
}


static int *
GetHostData()
{
    int i, msecs, t, x;
    enum clnt_stat clnt_stat;
    int intrs, maxtfer, ppersec, pag, spag, sum, swtchs;

    switch (PM->RpcVersion) {
#ifdef SOLARIS
        case RSTATVERS_VAR:

            if (PMD->OldTime == (int *) NULL) {
                PMD->StatsVar.dk_xfer.dk_xfer_val = (int *) NULL;
                PMD->StatsVar.cp_time.cp_time_val = (int *) NULL;
            }

            clnt_stat = clnt_call(PMD->Client, RSTATPROC_STATS,
                            (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_statsvar,
                            (caddr_t) &PMD->StatsVar, TIMEOUT);

            if (clnt_stat == RPC_TIMEDOUT) {
                return(NULL);
            }

            if (clnt_stat != RPC_SUCCESS) {
                if (!PM->IsSick) {
                    clnt_perror(PMD->Client, PM->AppClass);
                    PM->IsDead = TRUE;
                }
                return(NULL);
            }

            if (PMD->OldTime == (int *) 0) {
                /* Allocate memory for structures. */
                PMD->CpuStates = PMD->StatsVar.cp_time.cp_time_len;
                PMD->NDrive = PMD->StatsVar.dk_xfer.dk_xfer_len;
                PMD->OldTime = g_malloc0(PMD->CpuStates * sizeof(int));
                PMD->Xfer1 = g_malloc0(PMD->NDrive * sizeof(int));

                if ((PMD->OldTime == NULL) || (PMD->Xfer1 == NULL)) {
                    abort();
                }
            }    
      
            for (i = 0; i < PMD->CpuStates; i++) {
                t = PMD->StatsVar.cp_time.cp_time_val[i];
                PMD->StatsVar.cp_time.cp_time_val[i] -= PMD->OldTime[i];
                PMD->OldTime[i] = t;
            }
      
            /* Change for Mars -- only sum kernel and user.  See 1105770  */
            t = (PMD->StatsVar.cp_time.cp_time_val[0] + 
                 PMD->StatsVar.cp_time.cp_time_val[2]);

            x = (PMD->StatsVar.cp_time.cp_time_val[0] + 
                 PMD->StatsVar.cp_time.cp_time_val[1] +
                 PMD->StatsVar.cp_time.cp_time_val[2] + 
                 PMD->StatsVar.cp_time.cp_time_val[3]);
      
            if (x <= 0) {
                t++;
                PMD->BadCnt++;

                if (PMD->BadCnt >= PmTRIES) {
                    PM->IsSick = TRUE;
                }
            } else if (PM->IsSick) {
                PMD->BadCnt = 0;
                PM->IsSick = FALSE;
                PmRefresh(TRUE);
            } else {
                PMD->BadCnt = 0;
            }
      
            /* Calculate the meter data */

            PMD->Data[PM_CPU] = (int) ((100 * (double) t) / x);
      
            GETTIMEOFDAY(&PMD->Timeval, (void *) 0);

            msecs = (int) (1000 * ((double) PMD->Timeval.tv_sec -
                    PMD->OldTimeval.tv_sec) + 
                    (PMD->Timeval.tv_usec - PMD->OldTimeval.tv_usec) / 1000);
      
            /*
             * Bug #1013912 fix: in the rare occurence that msecs = 0,
             *  reset to msecs = 1 to ensure no "divide by zero" errors later.
             */
            if (msecs == 0) {
                msecs = 1;
            }
      
            sum = PMD->StatsVar.if_ipackets + PMD->StatsVar.if_opackets;
            ppersec = (int) (1000 * ((double) sum - PMD->TotalPackets) / msecs);

            PMD->TotalPackets = sum;
            PMD->Data[PM_PKTS] = ppersec;
      
            PMD->Data[PM_COLL] = (int) (FSCALE *
                                 ((double) PMD->StatsVar.if_collisions - 
                                 PMD->TotalCollisons) * 1000 / msecs);

            PMD->TotalCollisons = PMD->StatsVar.if_collisions;

            PMD->Data[PM_ERRS] = (int) (FSCALE *
                                 ((double) PMD->StatsVar.if_ierrors - 
                                 PMD->TotalErrors) * 1000 / msecs);

            PMD->TotalErrors = PMD->StatsVar.if_ierrors;
      
            if (!PMD->IsInit) {
                pag    = 0;
                spag   = 0;
                intrs  = 0;
                swtchs = 0;

                for (i = 0; i< PMD->NDrive; i++) {
                    /* Init the Xfer1 array to the first set of values */
                    PMD->Xfer1[i] = PMD->StatsVar.dk_xfer.dk_xfer_val[i];
                }
                PMD->IsInit = 1;
            } else { 
                pag = PMD->StatsVar.v_pgpgout + PMD->StatsVar.v_pgpgin - 
                      PMD->OldPage;
                pag = 1000 * pag / msecs;
                spag = PMD->StatsVar.v_pswpout + PMD->StatsVar.v_pswpin -
                       PMD->OldSwap;
                spag = 1000 * spag / msecs;
                intrs = PMD->StatsVar.v_intr - PMD->OldIntr;
                intrs = 1000 * intrs / msecs;
                swtchs = PMD->StatsVar.v_swtch - PMD->OldSwtch;
                swtchs = 1000 * swtchs / msecs;
            }
      
            PMD->OldPage = PMD->StatsVar.v_pgpgin + PMD->StatsVar.v_pgpgout;
            PMD->OldSwap = PMD->StatsVar.v_pswpin + PMD->StatsVar.v_pswpout;
            PMD->OldIntr = PMD->StatsVar.v_intr;
            PMD->OldSwtch = PMD->StatsVar.v_swtch;
            PMD->Data[PM_PAGE] = pag;
            PMD->Data[PM_SWAP] = spag;
            PMD->Data[PM_INTR] = intrs;
            PMD->Data[PM_CNTX] = swtchs;
            PMD->Data[PM_LOAD] = PMD->StatsVar.avenrun[0];

            for (i = 0;i < PMD->NDrive;i++) {
                t = PMD->StatsVar.dk_xfer.dk_xfer_val[i];
                PMD->StatsVar.dk_xfer.dk_xfer_val[i] -= PMD->Xfer1[i];
                PMD->Xfer1[i] = t;
            }

            maxtfer = PMD->StatsVar.dk_xfer.dk_xfer_val[0];
            /*
             * original method of calculating the disk activity
             * The "maxtfer" is initialized to the first xfer value and
             * then all of the drives are added in.
             * NOTE that the first one is added twice?
             */
            for (i = 0; i < PMD->NDrive; i++) {
                maxtfer += PMD->StatsVar.dk_xfer.dk_xfer_val[i];
            }
      
            maxtfer = (int) ((1000 * (double) maxtfer) / msecs);

            PMD->Data[PM_DISK] = maxtfer;
            PMD->OldTimeval = PMD->Timeval;

            return(PMD->Data);
#endif /*SOLARIS*/

        default:

            clnt_stat = clnt_call(PMD->Client, RSTATPROC_STATS, 
                             (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_statstime,
                             (caddr_t) &PMD->StatsTime, TIMEOUT);

            if (clnt_stat == RPC_TIMEDOUT) {
                return(NULL);
            }

            if (clnt_stat != RPC_SUCCESS) {
                if (!PM->IsSick) {
                    clnt_perror(PMD->Client, PM->AppClass);
                    PM->IsDead = TRUE;
                }
                return(NULL);
            }

            /* Allocate memory for structures */
            if (PMD->OldTime == (int *) 0) {
                PMD->CpuStates = 4;        /* Compatibility with version 3 */
                PMD->NDrive  = 4;
                PMD->OldTime = g_malloc0(PMD->CpuStates * sizeof(int));
                PMD->Xfer1   = g_malloc0(PMD->NDrive * sizeof(int));

                if ((PMD->OldTime == NULL) || (PMD->Xfer1 == NULL)) {
                    abort();
                }
            }    

            for (i = 0; i < PMD->CpuStates; i++) {
                t = PMD->StatsTime.cp_time[i];
                PMD->StatsTime.cp_time[i] -= PMD->OldTime[i];
                PMD->OldTime[i] = t;
            }
      
            /*
             * Change for mars -- only sum kernel and user  See 1105770
             * Here's how it works now:
             *
             * 0 = USER 
             * 1 = WAIT
             * 2 = KERNEL
             * 3 = IDLE
             *
             * NOTE - this is not how it works in <sys/sysinfo.h>. That file
             * is how the info gets from the kernel to rpc.rstatd, not how it
             * gets from there to here. It had been that user,
             * wait, and kernel were added up to get how busy the CPU was, but
             * under mars, machines (SS10's in particular) spend their free
             * time in wait, so perfmeter got pegged even when your machine 
             * was waiting.
             */

            t = PMD->StatsTime.cp_time[0] + PMD->StatsTime.cp_time[2];

            x = (PMD->StatsTime.cp_time[0] + PMD->StatsTime.cp_time[1] +
                 PMD->StatsTime.cp_time[2] + PMD->StatsTime.cp_time[3]);
      
            if (x <= 0) {
                t++;
                PMD->BadCnt++;

                if (PMD->BadCnt >= PmTRIES) {
                    PM->IsSick = TRUE;
                }
            } else if (PM->IsSick) {
                PMD->BadCnt = 0;
                PM->IsSick = FALSE;
            } else {
                PMD->BadCnt = 0;
            }
      
            PMD->Data[PM_CPU] = (int) ((100 * (double) t) / x);
      
            GETTIMEOFDAY(&PMD->Timeval, (void *) 0);

            msecs = (int) (1000 * ((double) PMD->Timeval.tv_sec -
                    PMD->OldTimeval.tv_sec) +
                    (PMD->Timeval.tv_usec - PMD->OldTimeval.tv_usec) / 1000);
      
            /*
             * Bug #1013912 fix: in the rare occurrence that msecs = 0,
             * reset to msecs = 1 to ensure no "divide by zero" errors later
             */
      
            if (msecs < 1) {
                msecs = 1;
            }
      
            sum = PMD->StatsTime.if_ipackets + PMD->StatsTime.if_opackets;
            ppersec = (int) (1000 * ((double) sum - PMD->TotalPackets) / msecs);
            PMD->TotalPackets = sum;
            PMD->Data[PM_PKTS] = ppersec;
      
            PMD->Data[PM_COLL] = (int) (FSCALE *
                                 ((double) PMD->StatsTime.if_collisions - 
                                 PMD->TotalCollisons) * 1000 / msecs);

            PMD->TotalCollisons = PMD->StatsTime.if_collisions;

            PMD->Data[PM_ERRS] = (int) (FSCALE *
                                 ((double) PMD->StatsTime.if_ierrors - 
                                 PMD->TotalErrors) * 1000 / msecs);

            PMD->TotalErrors = PMD->StatsTime.if_ierrors;
      
            if (!PMD->IsInit) {
                pag = 0;
                spag = 0;
                intrs = 0;
                swtchs = 0;

                for (i = 0; i < PMD->NDrive; i++) {
                    /* Init the Xfer1 array to the first set of values */
                    PMD->Xfer1[i] = PMD->StatsTime.dk_xfer[i];
                }
                PMD->IsInit = 1;
            } else { 
                pag = PMD->StatsTime.v_pgpgout + PMD->StatsTime.v_pgpgin -
                      PMD->OldPage;
                pag = 1000 * pag / msecs;
                spag = PMD->StatsTime.v_pswpout + PMD->StatsTime.v_pswpin -
                       PMD->OldSwap;
                spag = 1000 * spag / msecs;
                intrs = PMD->StatsTime.v_intr - PMD->OldIntr;
                intrs = 1000 * intrs / msecs;
                swtchs = PMD->StatsTime.v_swtch - PMD->OldSwtch;
                swtchs = 1000 * swtchs / msecs;
            }
            PMD->OldPage = PMD->StatsTime.v_pgpgin + PMD->StatsTime.v_pgpgout;
            PMD->OldSwap = PMD->StatsTime.v_pswpin + PMD->StatsTime.v_pswpout;
            PMD->OldIntr = PMD->StatsTime.v_intr;
            PMD->OldSwtch = PMD->StatsTime.v_swtch;
            PMD->Data[PM_PAGE] = pag;
            PMD->Data[PM_SWAP] = spag;
            PMD->Data[PM_INTR] = intrs;
            PMD->Data[PM_CNTX] = swtchs;
            PMD->Data[PM_LOAD] = PMD->StatsTime.avenrun[0];

            for (i = 0; i < PMD->NDrive; i++) {
                t = PMD->StatsTime.dk_xfer[i];
                PMD->StatsTime.dk_xfer[i] -= PMD->Xfer1[i];
                PMD->Xfer1[i] = t;
            }
      
            maxtfer = PMD->StatsTime.dk_xfer[0];

            /* 
             * Original method of calculating the disk activity
             * the "maxtfer" is initialized to the first xfer value and
             * then all of the drives are added in.
             * NOTE that the first one is added twice?
             */
            for (i = 0; i < PMD->NDrive; i++) {
                maxtfer += PMD->StatsTime.dk_xfer[i];
            }
      
            maxtfer = (int) (1000 * (double) maxtfer) / msecs;

            PMD->Data[PM_DISK] = maxtfer;
            PMD->OldTimeval = PMD->Timeval;

            return(PMD->Data);
    }
}


/* Update the meter data. */

int
PmUpdateData()
{
    int i, *data, old, value;
    PmMeterStruct *mp;

    data = GetHostData();

    if (PM->IsDead) {
        return(FALSE);
    }


    if (data == NULL) {
        PM->IsDead = TRUE;
        PM->IsSick = FALSE;
        RetryConnection();

        return(FALSE);
    }
  
    old = PM->CData;

    if (old < 0) {
        old = 0;
    }

    /* Increment the current data index */
    PM->CData++;

    if (PM->NData < PM->MData) {
        /* increment the total number of data points */
        PM->NData++;
    }

#ifdef DEBUGDATA
    g_print("UpdateData: Prev = %d, CData = %d, NData = %d, MData = %d ",
            old, PM->CData, PM->NData, PM->MData);
#endif

    /* Check if we are at the end of the array */
    if (PM->CData == PM->MData) {
        /* reset to beginning */
        PM->CData = 0;
    }

#ifdef DEBUGDATA
    g_print("CData = %d ", PM->CData);
#endif

    /* Loop over all the meters and set the new data point in the data array */
    for (i = 0, mp = PM->Meters; i < PmMAX_METERS; i++, mp++, data++) {
        if (*data < 0) {
            /* No data for this meter */
            *data = 0;
        }

        value = ((PM->LongExp * mp->Data[old]) +
            ((*data * FSCALE) / mp->Scale * (FSCALE - PM->LongExp))) >> FSHIFT;

        /* Check for wrap around */
        if (value < 0) {
            value = mp->CurMax * FSCALE;
        }

        /* Store the new data value */
        mp->Data[PM->CData] = value;

        /* Calculate the new LongAvg */
        value = ((PM->ShortExp * mp->LongAvg) +
                ((*data * FSCALE / mp->Scale) *
                (FSCALE - PM->ShortExp))) >> FSHIFT;

        /* Check for wrap around */
        if (value < 0) {
            value = mp->CurMax * FSCALE;
        }

        mp->LongAvg = value;
    }

#ifdef DEBUGDATA
    g_print("DONE\n");
    FFLUSH(stdout);
#endif

    return(TRUE);
}


/* Resize the meter data arrays. */

void
PmResizeMeterData(int size)
{
    int i, n, *data;

    if (size > PM->MData) {
        n = size + PmDATA_INCREMENT;

        for (i = 0; i < PmMAX_METERS; i++) {
            if (PM->MData == 0 || PM->NData < PM->MData || PM->CData < 0) {
                if (PM->Meters[i].Data) {
#ifdef DEBUGDATA
                    g_print("Reallocate meter %d data size = %d\n", i, n);
#endif
                    /* Preserve the data order */
                    PM->Meters[i].Data = (int *)
                        g_realloc(PM->Meters[i].Data, n * sizeof(int));
                } else {
#ifdef DEBUGDATA
                    g_print("Allocate meter %d data size = %d\n", i, n);
#endif
                    /* allocate a new array */
                    PM->Meters[i].Data = g_new0(int, n);
                }
            } else {
                data = g_new0(int, n);

#ifdef DEBUGDATA
                g_print("Reallocate & Reorder meter %d data size = %d,", i, n);

                g_print(" (0,%d,%d), (%d,%d,%d)\n",
                        PM->CData + 1, PM->NData - PM->CData - 1, 
                        PM->CData + 1, 0, PM->CData + 1);
#endif
                /* Transfer & reorder the data */
                g_memmove(data, PM->Meters[i].Data + PM->CData + 1,
                          PM->NData - PM->CData - 1);
                g_memmove(data + PM->CData + 1, PM->Meters[i].Data,
                          PM->CData + 1);

                g_free(PM->Meters[i].Data);
                PM->Meters[i].Data = data;
            }
        }

#ifdef DEBUGDATA
        g_print("Previous: CData = %d, NData = %d, MData = %d\n",
                PM->CData,PM->NData, PM->MData);
#endif

        PM->CData = PM->NData - 1;
        PM->MData = n;

#ifdef DEBUGDATA
        g_print("Current:  CData = %d, NData = %d, MData = %d\n",
                PM->CData, PM->NData, PM->MData);
#endif
    }
}


/* Reset the meter data. */

void
PmResetMeterData()
{
    int i;

    PM->CData = -1;
    PM->NData = 0;

    /* Clear the data array */
    for (i = 0; i < PmMAX_METERS; i++) {
        MEMSET(PM->Meters[i].Data, 0, PM->MData);
        PM->Meters[i].LongAvg = 0;
        PM->Meters[i].UnderCnt = 0;
    }

    PMD->IsInit = 0;
    if (PMD->OldTime) {
        g_free(PMD->OldTime);
        g_free(PMD->Xfer1);
        PMD->OldTime = PMD->Xfer1 = 0;
    }
    PMD->TotalPackets = PMD->TotalErrors = PMD->TotalCollisons = 0;
    PMD->BadCnt = 0;
    MEMSET((char *) &PMD->OldTimeval, 0, sizeof(struct timeval));
}


/* Scale the data. */

void
PmScaleData()
{
    int i, p, curmax;
    PmMeterStruct *M;

    /* Check the limits */

    for (i = 0; i < PmMAX_METERS; i++) {
        M = &PM->Meters[i];

        p = M->Data[PM->CData];
        curmax = M->CurMax;
        M->Value = curmax * ((double) p / (curmax * FSCALE));

        /* If the data is off scale, increase max up to upper limit */
        if (p > curmax * FSCALE) {
            while (((curmax * FSCALE) < p) && ((2 * curmax) <= M->MaxMax)) {
                curmax *= 2;
            }
            M->CurMax = curmax;
            PmResetCurMax(i);
            M->Redisplay = TRUE;
            continue;
        }

        /*
         * If all data is under 1/3 of max, decrease max down to limit of
         * minmax
         */

        if (p < (curmax * FSCALE) / 3) {
            M->UnderCnt++;
        } else {
            M->UnderCnt = 0;
        }

        if (M->UnderCnt > 0) {
            if ((PM->NData < 2) ||
                (M->UnderCnt >= PM->MeterWp) || 
                (M->UnderCnt >= PM->NData)) {
                    while (curmax / 2 >= M->MinMax) {
                        curmax /= 2;
                        if (curmax == M->MinMax) {
                            break;
                        }
                    }

                 M->UnderCnt = 0;
                 M->CurMax = curmax;
                 PmAssignMax(M->CurMax, 1);
                 PmResetCurMax(i);
                 M->Redisplay = TRUE;
                 continue;
            }
        }
    }
}


/* Write the sample data header record to the log file. */

static void 
WriteSampleHeader()
{
    FPUTS("%x\n", PM->SampleFP);
    FFLUSH(PM->SampleFP);
    PM->LogLineCount = 0;
}


/* Write the sample data to the log file. */

static void 
WriteSampleData()
{
    struct tm *tm;
    gint clock, i;
    gchar *date;

    clock = time((time_t *) 0);
    tm = localtime((time_t *) &clock);

    date = g_strdup_printf("%02d/%02d/%04d %02d:%02d:%02d",
                           tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900,
                           tm->tm_hour, tm->tm_min, tm->tm_sec);

    FPRINTF(PM->SampleFP, "%s ", date);
    g_free(date);

    if (PM->IsRemote) {
        FPRINTF(PM->SampleFP, "%s ", PM->Hostname);
    } else {
        FPRINTF(PM->SampleFP, "%s ", PM->LocalMachine);
    }

    for (i = 0; i < PmMAX_METERS; i++) {
        if (PM->Meters[i].Showing) {
            FPRINTF(PM->SampleFP, "\t%s = %.2f", PM->Meters[i].Name,
                    PM->Meters[i].Value);
        }
    }

    FPUTS("\n", PM->SampleFP);
    FFLUSH(PM->SampleFP);
}


/* Close the log file. */

void
PmLogClose()
{
    if (PM->SampleFP) {
        FCLOSE(PM->SampleFP);
        PM->SampleFP = NULL;
        PM->Log = FALSE;
    }
}


/* Open the log file. */

int
PmLogOpen()
{
    gchar *name = NULL;

    PmLogClose();

    if (strlen(PM->LogFilename) == 0) {
        name = g_strdup_printf("%s/%s.log%ld", g_get_home_dir(), PM->AppName,
                               getpid());
        PM->LogFilename = expand_tilde(name);
	g_free (name);
    }
  
    if ((PM->SampleFP = fopen(PM->LogFilename, "a")) == NULL) {
        return(FALSE);
    } else  {
        WriteSampleHeader();
    }

    return(TRUE);
}


/* Log the last data point. */

int
PmLogData()
{
    if (!PM->SampleFP && !PmLogOpen()) {
        return(FALSE);
    }

    /* Write the entry */
    if ((PM->LogLineCount % PM->LogPageLength) == 0) {
        WriteSampleHeader();
    }
  
    WriteSampleData();
    PM->LogLineCount++;
  
    return(TRUE);
}


/* Set the data scale factors . */

void
PmSetScaleFactors()
{
    /* Calculate the some data scaling factors */
    PM->ShortExp = (int) ((1 - 
                         ((double) (PM->SampleInterval*PM->SampleModifier) / 
                         PM->SampleInterval)) * FSCALE);

    PM->LongExp = (int) ((1 - 
                        ((double) (PM->SampleInterval * PM->SampleModifier) /
                        PM->SampleInterval)) * FSCALE);
}
