/*
 * Copyright (c) 1994, 2004 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Use, disclosure, copying, distribution, or transmission of of this code without
 * express permission is prohibited.
 */

#include "mecha.h"

#ifdef LINUX

/* Have faith; it's really here but you need a weird collection
   of define's to get the declaration for it. */

extern int getloadavg(double loadavg[], int nelem); 

static unsigned int sysGstart_time=0;

#define PAGESIZE 4096

int sysFnet_usage(uint32_t *obytes, uint32_t *ibytes,
                  uint32_t *opkts, uint32_t *ipkts) 
{
    int fd;
    char *stack[24];
    char buf[4096];
    size_t len;
    int lines, i;

    fd=open("/proc/net/dev",O_RDONLY);
    if(fd==-1) {
        logFmsg(2,"Cannot open /proc/net/dev. [e: %d]", errno);
        return -1;
    }

    len=read(fd,buf,4095);
    close(fd);
    buf[len]=0;

    lines=strFsplit(buf,stack,24,'\n','"');
    lines--;

    if(lines<2) {
        logFmsg(2,"Unable to parse the /proc/net/dev file. Invalid format.");
        return -1;
    }

    unsigned long ob=0, op=0, ib=0, ip=0;

    char *p;

    for(i=2;i<lines;i++) {
      p=stack[i];
      p+=7;
      ib+=strtoul(p,&p,0);
      ip+=strtoul(p,&p,0);
      p=(char *)strFskip_token(p);
      p=(char *)strFskip_token(p);
      p=(char *)strFskip_token(p);
      p=(char *)strFskip_token(p);
      p=(char *)strFskip_token(p);
      p=(char *)strFskip_token(p);
      ob+=strtoul(p,&p,0);
      op+=strtoul(p,&p,0);
      
    }
  
     *obytes=(uint32_t)ob;
     *opkts=(uint32_t)op;
     *ibytes=(uint32_t)ib;
     *ipkts=(uint32_t)ip;

    return 0;
}

int sysFmem_usage(uint32_t *pages_total, uint32_t *pages_free,
                  uint32_t *phys_mem, uint32_t *availr_mem,
                  uint32_t *free_mem) 
{
    int fd;
    char *linestack[4];
    char buf[4096];
    size_t len;
    char *p;
    
    fd=open("/proc/meminfo", O_RDONLY);
    if(fd==-1) {
        logFmsg(2,"Cannot open /proc/meminfo. [e: %d]", errno);
        return -1;
    }

    len=read(fd,buf,4095);

    close(fd);

    buf[len]=0;

    /* Break up the first two lines of this file */
    if(strFsplit(buf,linestack,4,'\n','"')!=4) {
        logFmsg(2,"Error parsing /proc/meminfo file. Invalid format. Can't split lines.");
        return -1;
    }

    p=linestack[1];
    p=(char *)strFskip_token(p);

    unsigned long mem_total, mem_used, mem_free, mem_shared, mem_buffers, mem_cache;

    mem_total=strtoul(p,&p,10)/PAGESIZE;
    mem_used=strtoul(p,&p,10)/PAGESIZE;
    mem_free=strtoul(p,&p,10)/PAGESIZE;
    mem_shared=strtoul(p,&p,10)/PAGESIZE;
    mem_buffers=strtoul(p,&p,10)/PAGESIZE;
    mem_cache=strtoul(p,&p,10)/PAGESIZE;

    unsigned long swap_total, swap_used, swap_free;

    p=linestack[2];
    p=(char *)strFskip_token(p);

    swap_total=strtoul(p,&p,10)/PAGESIZE;
    swap_used=strtoul(p,&p,10)/PAGESIZE;
    swap_free=strtoul(p,&p,10)/PAGESIZE;

    *pages_total=(uint32_t)mem_total+swap_total;
    *pages_free=(uint32_t)mem_free+swap_free;
    *phys_mem=(uint32_t)mem_total;
    *availr_mem=(uint32_t)mem_free;
    *free_mem=(uint32_t)mem_free+mem_buffers+mem_cache;

    return 0;
}

int sysFget_load(double *one_min, double *five_min, double *fifteen_min)
{
    double x[3];
    
    if (getloadavg(x, 3) == -1) {
        logFmsg(2, "getloadvg failed [e: %d]", errno);
       return -1;
    }

    *one_min=x[0];
    *five_min=x[1];
    *fifteen_min=x[2];   
        
    return 0;
}

int sysFcpu_usage(uint32_t *idle_time, uint32_t *user_time,
		  uint32_t *kernel_time, uint32_t *wait_time)
{
    int fd;
    char *stack[6];
    char buf[4096];
    size_t len;
    char *p;
    
    fd=open("/proc/stat", O_RDONLY);
    if(fd==-1) {
        logFmsg(2,"Unable to open /proc/stat. [e: %d]", errno);
 	return -1;
    }
    
    len=read(fd,buf,4095);

    close(fd);
    
    buf[len]=0;
    
    if(strFsplit(buf,stack,6,' ','"')!=6) {
	logFmsg(2,"Error parsing /proc/stat file. Invalid format.");
	return -1;
    }
    
   /* The last line is going to have the hanging \n on it, which we
      need to strip off. -aleigh */
    
    for(p=stack[5];*p!=0;p++) {
	if(*p=='\n') {
	    *p=0; 
	    break;
	}
    }

    /* We don't get a metric for this so we are just always going to
       report 0. -aleigh */
    *wait_time=0;

    *user_time=utlFatoui(stack[2])+utlFatoui(stack[3]);
    *kernel_time=utlFatoui(stack[4]);
    *idle_time=utlFatoui(stack[5]);

    return 0;
}

unsigned int sysFboot_time(void) 
{
    int fd;
    char *stack[2];
    char buf[4096];
    size_t len;

    /* Because of the fractions in the output from 
       /proc/uptime the time otherwise returned may vary by 1 second during
       the course of the execution. Since the startup epoch is also a fixed
       time we just cache it. No synchronization is required. -aleigh */

    if(sysGstart_time!=0) {
        return sysGstart_time;
    }

    fd=open("/proc/uptime", O_RDONLY);
    if(fd==-1) {
        perror("/proc/uptime");
        exit(1);
    }

    len=read(fd,buf,4095);

    close(fd);

    buf[len]=0;

    if(strFsplit(buf,stack,2,' ','"')!=2) {
        logFmsg(2,"Error parsing /proc/uptime file. Invalid format.");
	return -1;
    }

    /* Inefficient but this should only happen once -aleigh */

    sysGstart_time=utlFtime()-utlFatoui(stack[0]);

    return utlFtime()-utlFatoui(stack[0]);
}

#endif

/*************************************************************************/

#ifdef SOLARIS

#include "kstat.h"
#include <sys/mnttab.h>
#include <sys/statvfs.h>
#ifdef HAVE_GETLOADAVG
#include <sys/loadavg.h>
#endif
#include <sys/sysinfo.h>

int sysFfs_info(lstTset * set)
{
    FILE *mnttab;
    struct mnttab me;
    struct statvfs fse;
    char nbuf[128];
    char vbuf[128];

    assert(set!=NULL);
    
    mnttab = fopen("/etc/mnttab", "r");
    
    if (mnttab == NULL) {
        logFmsg(2, "Unable to open /etc/mnttab [e: %d]", errno);
        return -1;
    }
    
    while (getmntent(mnttab, &me) == 0) {
        if (strcmp(me.mnt_fstype, "ufs") == 0
            || strcmp(me.mnt_fstype, "vxfs") == 0
            || strcmp(me.mnt_fstype, "tmpfs") == 0) {
            if (statvfs(me.mnt_mountp, &fse) != 0) {
                logFmsg(2, "statvfs() failed [e: %d] [mp: %s]",
                        errno, me.mnt_mountp);
            }
            sprintf(nbuf, "fs_%s_blocks", me.mnt_mountp);
            sprintf(vbuf, "%u", fse.f_blocks);
            lstFset_add(set, (char *) &nbuf, (char *) &vbuf);

            sprintf(nbuf, "fs_%s_bavail", me.mnt_mountp);
            sprintf(vbuf, "%u", fse.f_bavail);
            lstFset_add(set, (char *) &nbuf, (char *) &vbuf);

            sprintf(nbuf, "fs_%s_bsize", me.mnt_mountp);
            sprintf(vbuf, "%u", fse.f_bsize);
            lstFset_add(set, (char *) &nbuf, (char *) &vbuf);

            sprintf(nbuf, "fs_%s_files", me.mnt_mountp);
            sprintf(vbuf, "%u", fse.f_files);
            lstFset_add(set, (char *) &nbuf, (char *) &vbuf);
            
            sprintf(nbuf, "fs_%s_favail", me.mnt_mountp);
            sprintf(vbuf, "%u", fse.f_favail);
            lstFset_add(set, (char *) &nbuf, (char *) &vbuf);
        }
    }
    fclose(mnttab);
}

/*
 * PercentLoad = 100 * (1 - D/(D+Load))
 * where D determines which load value should be associated with which
 * load percentage. D=50 means that 128 is 50% load, 256 is 80%, etc.
 */
int sysFget_load(double *one_min, double *five_min, double *fifteen_min)
{
    double x[3];

#ifdef HAVE_GETLOADAVG    
    if (getloadavg(x, 3) == -1) {
        logFmsg(2, "getloadvg failed [e: %d]", errno);
       return -1;
    }
#else
    x[0]=0;
    x[1]=0;
    x[2]=0;
#endif

    *one_min=x[0];
    *five_min=x[1];
    *fifteen_min=x[2];

    return 0;
}

static kstat_ctl_t *sysGkc=NULL;
static pthread_mutex_t sysLkc=PTHREAD_MUTEX_INITIALIZER;

static kstat_ctl_t *sysFgetkc(void) {
    if(sysGkc!=NULL) {
        return sysGkc;
    }
    
    pthread_mutex_lock(&sysLkc);

    if(sysGkc!=NULL) {
            /* Someone else opened it */
        pthread_mutex_unlock(&sysLkc);
        return sysGkc;
    }

    sysGkc=kstat_open();
    pthread_mutex_unlock(&sysLkc);
    return sysGkc;
}

/*
 * Return the startup time of the host, in unix time.
 */
unsigned int sysFboot_time(void)
{

    kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *kn;

    kc = sysFgetkc();

    if (kc == NULL) {
        perror("kstat_open");
        abort();
    }
    ksp = kstat_lookup(kc, "unix", -1, "system_misc");

    kstat_read(kc, ksp, NULL);

    kn = kstat_data_lookup(ksp, "boot_time");

    switch (kn->data_type) {
#ifdef KSTAT_DATA_INT32
        case KSTAT_DATA_INT32:
            return kn->value.i32;
            break;
        case KSTAT_DATA_UINT32:
            return kn->value.ui32;
            break;
        case KSTAT_DATA_INT64:
            return kn->value.i64;
            break;
        case KSTAT_DATA_UINT64:
            return kn->value.ui64;
            break;
#endif
    }

    return -1;
}

int sysFmem_usage(uint32_t *pages_total, uint32_t *pages_free,
                  uint32_t *phys_mem, uint32_t *availr_mem,
                  uint32_t *free_mem)
{

    kstat_ctl_t *kc;
    kstat_t *ksp;
    kstat_named_t *kn;
    
    kc = sysFgetkc();

    if (kc == NULL) {
        logFmsg(2,"kstat_open failed [e: %d]", errno);
        return -1;
    }
    
    ksp = kstat_lookup(kc, "unix", -1, "system_pages");
    
    assert(ksp!=NULL);
    
    kstat_read(kc, ksp, NULL);
    
    kn = kstat_data_lookup(ksp, "pagestotal");
    assert(kn!=NULL);
    *pages_total = kn->value.ui32;
    
    kn = kstat_data_lookup(ksp, "pagesfree");
    assert(kn!=NULL);
    *pages_free = kn->value.ui32;
    
    kn = kstat_data_lookup(ksp, "physmem");
    assert(kn!=NULL);
    *phys_mem = kn->value.ui32;
    
    kn = kstat_data_lookup(ksp, "availrmem");
    assert(kn!=NULL);
    *availr_mem = kn->value.ui32;
    
    kn = kstat_data_lookup(ksp, "freemem");
    assert(kn!=NULL);
    *free_mem=kn->value.ui32;
    
    return 0;
}

struct sysScpu {
    kstat_t *ksp;
    struct sysScpu *next;
};
typedef struct sysScpu sysTcpu;

static sysTcpu *sysGcpus=NULL;
static pthread_mutex_t sysLcpus=PTHREAD_MUTEX_INITIALIZER;

static int sysFcpu_init(kstat_ctl_t *kc) {
    int i;
    kstat_t *ksp;
    sysTcpu *cpu;

    assert(kc!=NULL);

    for(i=0;i<128;i++) {
        ksp=kstat_lookup(kc,"cpu_stat",i,NULL);
        if(ksp==NULL) continue;

        logFmsg(3,"Found CPU: %d", i);
        cpu=(sysTcpu *)malloc(sizeof(sysTcpu));
        cpu->ksp=ksp;
        cpu->next=sysGcpus;
        sysGcpus=cpu;
    }

    return 0;
}

int sysFcpu_usage(uint32_t *idle_time, uint32_t *user_time,
                 uint32_t *kernel_time, uint32_t *wait_time)
{
    kstat_ctl_t *kc;
    sysTcpu *cpu;
    cpu_stat_t cpu_stat;

    kc=sysFgetkc();

    assert(kc!=NULL);

    *idle_time=0;
    *user_time=0;
    *kernel_time=0;
    *wait_time=0;
    
    if(sysGcpus==NULL) {
        pthread_mutex_lock(&sysLcpus);
        if(sysGcpus==NULL) {
            sysFcpu_init(kc);
        }
        pthread_mutex_unlock(&sysLcpus);
    }

    for(cpu=sysGcpus;cpu!=NULL;cpu=cpu->next) {
        assert(cpu!=NULL);
        assert(cpu->ksp!=NULL);

        if(kstat_read(kc,cpu->ksp,&cpu_stat)==-1) {
            return -1;
        }
        *idle_time += cpu_stat.cpu_sysinfo.cpu[0];
        *user_time += cpu_stat.cpu_sysinfo.cpu[1];
        *kernel_time += cpu_stat.cpu_sysinfo.cpu[2];
        *wait_time += cpu_stat.cpu_sysinfo.cpu[3];
    }

    return 0;
}

struct sysSnet {
    kstat_t *ksp;
    struct sysSnet *next;
};
typedef struct sysSnet sysTnet;

static sysTnet *sysGnet=NULL;
static pthread_mutex_t sysLnet = PTHREAD_MUTEX_INITIALIZER;

/* Must own sysLnet */
static void sysFnet_register(kstat_t *ksp) {
    sysTnet *net;

    assert(ksp!=NULL);
    
    net=(sysTnet *)malloc(sizeof(sysTnet));
    assert(net!=NULL);

    net->ksp=ksp;
    net->next=sysGnet;
    sysGnet=net;
}

static int sysFnet_init(kstat_ctl_t *kc) {
    kstat_t *ksp;
    int i;
    
    assert(kc!=NULL);

    for(i=0;i<128;i++) {
        ksp=kstat_lookup(kc,"hme",i,NULL);
        if(ksp!=NULL) {
            logFmsg(3,"Found network interface hme%d", i);
            sysFnet_register(ksp);
        }
        
        ksp=kstat_lookup(kc,"qfe",i,NULL);
        if(ksp!=NULL) {
            logFmsg(3,"Found network interface qfe%d", i);
            sysFnet_register(ksp);
        }
        
        ksp=kstat_lookup(kc,"le",i,NULL);
        if(ksp!=NULL) {
            logFmsg(3,"Found network interface le%d", i);
            sysFnet_register(ksp);
        }

        ksp=kstat_lookup(kc,"ge",i,NULL);
        if(ksp!=NULL) {
            logFmsg(3,"Found network interface ge%d", i);
            sysFnet_register(ksp);
        }
    }

    return i;
}

int sysFnet_usage(uint32_t *obytes, uint32_t *ibytes,
                  uint32_t *opkts, uint32_t *ipkts)
{
    kstat_ctl_t *kc;
    kstat_named_t *kn;
    sysTnet *net;

    kc=sysFgetkc();

    assert(kc!=NULL);

    *obytes=0;
    *ibytes=0;
    *opkts=0;
    *ipkts=0;
    
    if(sysGnet==NULL) {
        pthread_mutex_lock(&sysLnet);
        if(sysGnet==NULL) {
            sysFnet_init(kc);
        }
        pthread_mutex_unlock(&sysLnet);
    }

    for(net=sysGnet;net!=NULL;net=net->next) {
        kstat_read(kc,net->ksp,NULL);

        kn=kstat_data_lookup(net->ksp,"obytes");
        if(kn!=NULL) {
            *obytes+=kn->value.ui32;
        }

        kn=kstat_data_lookup(net->ksp,"rbytes");
        if(kn!=NULL) {
            *ibytes+=kn->value.ui32;
        }

        kn=kstat_data_lookup(net->ksp,"ipackets");
        if(kn!=NULL) {
            *ipkts+=kn->value.ui32;
        }
        
        kn=kstat_data_lookup(net->ksp,"opackets");
        if(kn!=NULL) {
            *opkts+=kn->value.ui32;
        }
    }
    
    return 0;
}

#endif
