#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/hardware/cpu.h>
#include "cpu_linux.h"

#include <unistd.h>
#include <fcntl.h>

#define CPU_FILE    "/proc/cpuinfo"
#define STAT_FILE   "/proc/stat"
#define VMSTAT_FILE "/proc/vmstat"


    /* Which field(s) describe the type of CPU */
#if defined(__i386__) || defined(__x86_64__)
#define DESCR_FIELD  "vendor_id"
#define DESCR2_FIELD "model name"
#endif
#if defined(__powerpc__) || defined(__powerpc64__)
#define DESCR_FIELD  "cpu\t"
#endif
#if defined(__ia64__)
	/* since vendor is always Intel ... we don't parse vendor */
#define DESCR_FIELD  "family"
#endif


    /*
     * Initialise the list of CPUs on the system
     *   (including descriptions)
     *
     * XXX - Assumes x86-style /proc/cpuinfo format
     *       See CPUinfo database at
     *           http://www.rush3d.com/gcc/
     *                for info on alternative styles
     */
void init_cpu_linux( void ) {
    FILE *fp;
    char buf[1024];
    int  i, n = 0;
    netsnmp_cpu_info *cpu = netsnmp_cpu_get_byIdx( -1, 1 );
    strcpy(cpu->name, "Overall CPU statistics");

    fp = fopen( CPU_FILE, "r" );
    if (!fp) {
        snmp_log(LOG_ERR, "Can't open procinfo file %s\n", CPU_FILE);
        return;
    }
    while ( fgets( buf, sizeof(buf), fp)) {
        if ( sscanf( buf, "processor : %d", &i ) == 1)  {
            n++;
            cpu = netsnmp_cpu_get_byIdx( i, 1 );
            cpu->status = 2;  /* running */
            sprintf( cpu->name, "cpu%d", i );
#if defined(__s390__) || defined(__s390x__)
            strlcat(cpu->descr, "An S/390 CPU", sizeof(cpu->descr));
#endif
        }
#if defined(__s390__) || defined(__s390x__)
	/* s390 may have different format of CPU_FILE */
        else {
            if (sscanf( buf, "processor %d:", &i ) == 1)  {
                n++;
                cpu = netsnmp_cpu_get_byIdx( i, 1 );
                cpu->status = 2;  /* running */
                sprintf(cpu->name, "cpu%d", i);
                strlcat(cpu->descr, "An S/390 CPU", sizeof(cpu->descr));
            }
        }
#endif

#ifdef DESCR_FIELD
        if (!strncmp( buf, DESCR_FIELD, strlen(DESCR_FIELD))) {
            char *cp = strchr( buf, ':' );
            if (cp) {
                strlcpy(cpu->descr, cp + 2, sizeof(cpu->descr));
                cp = strchr(cpu->descr, '\n');
                if (cp)
                    *cp = 0;
            }
        }
#endif
#ifdef DESCR2_FIELD
        if (!strncmp( buf, DESCR2_FIELD, strlen(DESCR2_FIELD))) {
            char *cp = strchr( buf, ':' );
            if (cp) {
                strlcat(cpu->descr, cp, sizeof(cpu->descr));
                cp = strchr(cpu->descr, '\n');
                if (cp)
                    *cp = 0;
            }
        }
#endif
    }
    fclose(fp);
    cpu_num = n;
}

void _cpu_load_swap_etc( char *buff, netsnmp_cpu_info *cpu );

    /*
     * Load the latest CPU usage statistics
     */
int netsnmp_cpu_arch_load( netsnmp_cache *cache, void *magic ) {
    static char *buff  = NULL;
    static int   bsize = 0;
    static int   first = 1;
    static int   num_cpuline_elem = 0;
    int          bytes_read, statfd, i;
    char        *b1, *b2;
    unsigned long long cusell = 0, cicell = 0, csysll = 0, cidell = 0,
                       ciowll = 0, cirqll = 0, csoftll = 0, cstealll = 0,
                       cguestll = 0, cguest_nicell = 0;
    netsnmp_cpu_info* cpu;

    if ((statfd = open(STAT_FILE, O_RDONLY, 0)) == -1) {
        snmp_log_perror(STAT_FILE);
        return -1;
    }
    if (bsize == 0) {
        bsize = getpagesize()-1;
        buff = (char*)malloc(bsize+1);
        if (buff == NULL) {
            close(statfd);
            return -1;
        }
    }
    while ((bytes_read = read(statfd, buff, bsize)) == bsize) {
        bsize += BUFSIZ;
        buff = (char*)realloc(buff, bsize+1);
        DEBUGMSGTL(("cpu", "/proc/stat buffer increased to %d\n", bsize));
        close(statfd);
        statfd = open(STAT_FILE, O_RDONLY, 0);
        if (statfd == -1) {
            snmp_log_perror(STAT_FILE);
            return -1;
	}
    }
    close(statfd);

    if ( bytes_read < 0 ) {
        snmp_log_perror(STAT_FILE "read error");
        return -1;
    }
    buff[bytes_read] = '\0';

        /*
         * CPU statistics (overall and per-CPU)
         */
    b1 = buff;
    while ((b2 = strstr( b1, "cpu" ))) {
        if (b2[3] == ' ') {
            cpu = netsnmp_cpu_get_byIdx( -1, 0 );
            if (!cpu) {
                snmp_log_perror("No (overall) CPU info entry");
                return -1;
            }
            b1 = b2+4; /* Skip "cpu " */
        } else {
            sscanf( b2, "cpu%d", &i );
                       /* Create on the fly to support non-x86 systems - see init */
            cpu = netsnmp_cpu_get_byIdx( i, 1 );
            if (!cpu) {
                snmp_log_perror("Missing CPU info entry");
                break;
            }
            b1 = b2; /* Skip "cpuN " */
            while(*b1 != ' ') b1++;
            b1++;
        }

        num_cpuline_elem = sscanf(b1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
         &cusell, &cicell, &csysll, &cidell, &ciowll, &cirqll, &csoftll, &cstealll, &cguestll, &cguest_nicell);
        DEBUGMSGTL(("cpu", "/proc/stat cpu line number of elements: %i\n", num_cpuline_elem));

        /* kernel 2.6.33 and above */
        if (num_cpuline_elem == 10) {
            cpu->guestnice_ticks = cguest_nicell;
        }
        /* kernel 2.6.24 and above */
        if (num_cpuline_elem >= 9) {
            cpu->guest_ticks = cguestll;
        }
        /* kernel 2.6.11 and above */
        if (num_cpuline_elem >= 8) {
            cpu->steal_ticks = cstealll;
        }
        /* kernel 2.6 */
        if (num_cpuline_elem >= 5) {
            cpu->wait_ticks   = ciowll;
            cpu->intrpt_ticks = cirqll;
            cpu->sirq_ticks   = csoftll;
        }
        /* rest */
        cpu->user_ticks = cusell;
        cpu->nice_ticks = cicell;
        cpu->sys_ticks  = csysll;
        cpu->idle_ticks = cidell;
    }
    if ( b1 == buff ) {
	if (first)
	    snmp_log(LOG_ERR, "No cpu line in %s\n", STAT_FILE);
    }

        /*
         * Interrupt/Context Switch statistics
         *   XXX - Do these really belong here ?
         */
    cpu = netsnmp_cpu_get_byIdx( -1, 0 );
    _cpu_load_swap_etc( buff, cpu );

    /*
     * XXX - TODO: extract per-CPU statistics
     *    (Into separate netsnmp_cpu_info data structures)
     */

    first = 0;
    return 0;
}


        /*
         * Interrupt/Context Switch statistics
         *   XXX - Do these really belong here ?
         */
void _cpu_load_swap_etc( char *buff, netsnmp_cpu_info *cpu ) {
    static int   has_vmstat = 1;
    static char *vmbuff  = NULL;
    static int   vmbsize = 0;
    static int   first   = 1;
    int          bytes_read, vmstatfd;
    char        *b;
    unsigned long long pin, pout, swpin, swpout;
    unsigned long long itot, iticks, ctx;

    if (has_vmstat) {
      vmstatfd = open(VMSTAT_FILE, O_RDONLY, 0);
      if (vmstatfd == -1 ) {
            snmp_log(LOG_ERR, "cannot open %s\n", VMSTAT_FILE);
            has_vmstat = 0;
      } else {
        if (vmbsize == 0) {
	    vmbsize = getpagesize()-1;
	    vmbuff = (char*)malloc(vmbsize+1);
        }
        while ((bytes_read = read(vmstatfd, vmbuff, vmbsize)) == vmbsize) {
	    vmbsize += BUFSIZ;
	    vmbuff = (char*)realloc(vmbuff, vmbsize+1);
	    close(vmstatfd);
	    vmstatfd = open(VMSTAT_FILE, O_RDONLY, 0);
	    if (vmstatfd == -1) {
                snmp_log_perror("cannot open " VMSTAT_FILE);
                return;
	    }
        }
        close(vmstatfd);
        if ( bytes_read < 0 ) {
            snmp_log_perror(VMSTAT_FILE "read error");
            return;
        }
        vmbuff[bytes_read] = '\0';
      }
    }

    if (has_vmstat) {
	b = strstr(vmbuff, "pgpgin ");
	if (b) {
	    sscanf(b, "pgpgin %llu", &pin);
            cpu->pageIn  = (unsigned long long)pin*2;  /* ??? */
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No pgpgin line in %s\n", VMSTAT_FILE);
            cpu->pageIn  = 0;
	}
	b = strstr(vmbuff, "pgpgout ");
	if (b) {
	    sscanf(b, "pgpgout %llu", &pout);
            cpu->pageOut = (unsigned long long)pout*2;  /* ??? */
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No pgpgout line in %s\n", VMSTAT_FILE);
            cpu->pageOut = 0;
	}
	b = strstr(vmbuff, "pswpin ");
	if (b) {
	    sscanf(b, "pswpin %llu", &swpin);
            cpu->swapIn  = (unsigned long long)swpin;
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No pswpin line in %s\n", VMSTAT_FILE);
            cpu->swapIn  = 0;
	}
	b = strstr(vmbuff, "pswpout ");
	if (b) {
	    sscanf(b, "pswpout %llu", &swpout);
            cpu->swapOut = (unsigned long long)swpout;
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No pswpout line in %s\n", VMSTAT_FILE);
            cpu->swapOut = 0;
	}
    }
    else {
	b = strstr(buff, "page ");
	if (b) {
	    sscanf(b, "page %llu %llu", &pin, &pout);
            cpu->pageIn  = (unsigned long long)pin;
            cpu->pageOut = (unsigned long long)pout;
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No page line in %s\n", STAT_FILE);
            cpu->pageIn  = cpu->pageOut = 0;
	}
	b = strstr(buff, "swap ");
	if (b) {
	    sscanf(b, "swap %llu %llu", &swpin, &swpout);
            cpu->swapIn  = (unsigned long long)swpin;
            cpu->swapOut = (unsigned long long)swpout;
	} else {
	    if (first)
		snmp_log(LOG_ERR, "No swap line in %s\n", STAT_FILE);
            cpu->swapIn  = cpu->swapOut = 0;
	}
    }

    b = strstr(buff, "intr ");
    if (b) {
	sscanf(b, "intr %llu %llu", &itot, &iticks);
        cpu->nInterrupts = (unsigned long long)itot;
        /* iticks not used? */
    } else {
	if (first)
	    snmp_log(LOG_ERR, "No intr line in %s\n", STAT_FILE);
    }
    b = strstr(buff, "ctxt ");
    if (b) {
	sscanf(b, "ctxt %llu", &ctx);
        cpu->nCtxSwitches = (unsigned long long)ctx;
    } else {
	if (first)
	    snmp_log(LOG_ERR, "No ctxt line in %s\n", STAT_FILE);
    }
    first = 0;
}
