/*
 * snmpps.c - display process table on a network entity via SNMP.
 *
 */

/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/***********************************************************************
        Copyright 1988, 1989, 1991, 1992 by Carnegie Mellon University

                      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.  

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
******************************************************************/

#include <net-snmp/net-snmp-config.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <sys/types.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <stdio.h>
#include <ctype.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_NCURSES_CURSES_H
#include <ncurses/curses.h>
#elif HAVE_CURSES_H
#include <curses.h>
#endif
#include <signal.h>

#include <net-snmp/net-snmp-includes.h>

void
usage(void)
{
    fprintf(stderr, "Usage: snmpps [-Cp] [-Ca] [-C m | n | t] AGENT");
    snmp_parse_args_usage(stderr);
    fprintf(stderr, "\n\n");
    snmp_parse_args_descriptions(stderr);
    fprintf(stderr, "\nsnmpps options:\n");
    fprintf(stderr,
            "\t-Cp\tShow hrSWRunPath instead of hrSWRunName\n");
    fprintf(stderr,
            "\t-Ca\tShow hrSWRunParameters in addition to hrSWRunName/Path\n");
    fprintf(stderr,
            "\t-Ct\tSort processes according to CPU time used\n");
    fprintf(stderr,
            "\t-Cm\tSort processes according to memory usage\n");
    fprintf(stderr,
            "\t-Cn\tSort processes by PID number (default)\n");
}

int   command_args = 0,
      command_path = 0;
int   topsort = 'c';
char *progname;

static void
optProc(int argc, char *const *argv, int opt)
{
    switch (opt) {
    case 'C':
        while (*optarg) {
            switch (*optarg++) {
            case 'a':
                command_args = 1;
                break;
            case 'p':
                command_path = 1;
                break;
            case 'm':
                topsort = 'm';
                break;
            case 'n':
                topsort = 'n';
                break;
            case 't':
                topsort = 't';
                break;

            default:
                fprintf(stderr,
                        "Unknown flag passed to -C: %c\n", optarg[-1]);
                exit(1);
            }
        }
    }
}

struct hrSWRunTable {
    u_long  hrSWRunIndex;
    char   *hrSWRunName;
    char   *hrSWRunPath;
    char   *hrSWRunParameters;
    u_long  hrSWRunID;
    u_long  hrSWRunType;
    u_long  hrSWRunStatus;
    u_long  hrSWRunPerfMem;
    u_long  hrSWRunPerfCPU;
    u_long  hrSWRunPerfCPUInc;
};

struct cpuStats {
    u_long user;
    u_long nice;
    u_long system;
    u_long idle;
    u_long wait;
    u_long kernel;
    u_long intr;
    u_long softintr;
    u_long steal;
    u_long guest;
    u_long guestnice;
};

struct memStats {
    u_long totalSwap;
    u_long availSwap;
    u_long totalReal;
    u_long availReal;
    u_long shared;
    u_long buffer;
    u_long cached;
};

struct laStats {
    u_long la1;
    u_long la5;
    u_long la15;
};


int
add(netsnmp_pdu *pdu, const char *mibnodename,
    oid * index, size_t indexlen)
{
    oid             base[MAX_OID_LEN];
    size_t          base_length = MAX_OID_LEN;

    memset(base, 0, MAX_OID_LEN * sizeof(oid));

    if (!snmp_parse_oid(mibnodename, base, &base_length)) {
        snmp_perror(mibnodename);
        fprintf(stderr, "couldn't find mib node %s, giving up\n",
                mibnodename);
#if HAVE_CURSES_H
        endwin();
#endif
        exit(1);
    }

    if (base_length + indexlen > sizeof(base) / sizeof(base[0])) {
        fprintf(stderr, "internal error for %s, giving up\n", mibnodename);
        exit(1);
    }

    if (index && indexlen) {
        memcpy(&(base[base_length]), index, indexlen * sizeof(oid));
        base_length += indexlen;
    }
    DEBUGMSGTL(("add", "created: "));
    DEBUGMSGOID(("add", base, base_length));
    DEBUGMSG(("add", "\n"));
    snmp_add_null_var(pdu, base, base_length);

    return base_length;
}

netsnmp_variable_list *
collect_procs(netsnmp_session *ss, netsnmp_pdu *pdu,
        oid * base, size_t base_length)
{
    netsnmp_pdu    *response;
    int             running = 1;
    netsnmp_variable_list *saved = NULL, **vlpp = &saved;
    int             status;

    while (running) {
        /*
         * gotta catch em all, gotta catch em all! 
         */
        status = snmp_synch_response(ss, pdu, &response);
        if (status != STAT_SUCCESS || !response) {
            snmp_sess_perror(progname, ss);
#if HAVE_CURSES_H
            endwin();
#endif
            exit(1);
        }
        if (response->errstat != SNMP_ERR_NOERROR) {
            fprintf(stderr, "%s: Error in packet: %s\n", progname,
                    snmp_errstring(response->errstat));
#if HAVE_CURSES_H
            endwin();
#endif
            exit(1);
        }
        if (snmp_oid_compare(response->variables->name,
                             SNMP_MIN(base_length,
                                      response->variables->name_length),
                             base, base_length) != 0)
            running = 0;
        else if (response->variables->type == SNMP_NOSUCHINSTANCE ||
                    response->variables->type == SNMP_NOSUCHOBJECT ||
                response->variables->type == SNMP_ENDOFMIBVIEW)
            running = 0;
        else {
            /*
             * get response 
             */
            *vlpp = response->variables;
            (*vlpp)->next_variable = NULL;      /* shouldn't be any, but just in case */

            /*
             * create the next request 
             */
            pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
            snmp_add_null_var(pdu, (*vlpp)->name, (*vlpp)->name_length);

            /*
             * finish loop setup 
             */
            vlpp = &((*vlpp)->next_variable);
            response->variables = NULL; /* ahh, forget about it */
        }
        snmp_free_pdu(response);
    }
    return saved;
}


int
collect_perf(netsnmp_session *ss, struct hrSWRunTable **fproc)
{
    netsnmp_pdu    *pdu;
    netsnmp_pdu    *response;
    netsnmp_variable_list *vlp;
    oid             base[MAX_OID_LEN];
    size_t          base_length;
    int status, count = 0;
    struct hrSWRunTable *procs = malloc(sizeof(struct hrSWRunTable));

    pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
    base_length =
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunIndex", NULL, 0);
    memcpy(base, pdu->variables->name, base_length * sizeof(oid));

    vlp = collect_procs(ss, pdu, base, base_length);

    while (vlp) {
        size_t len;
        struct hrSWRunTable proc;
        netsnmp_variable_list *vlp2;

        pdu = snmp_pdu_create(SNMP_MSG_GET);

        add(pdu, "HOST-RESOURCES-MIB:hrSWRunName",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunID",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunPath",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunParameters",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunType",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunStatus",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunPerfCPU",
            &vlp->name[base_length], vlp->name_length - base_length);
        add(pdu, "HOST-RESOURCES-MIB:hrSWRunPerfMem",
            &vlp->name[base_length], vlp->name_length - base_length);

        response = NULL;
        status = snmp_synch_response(ss, pdu, &response);
        if (status != STAT_SUCCESS || !response) {
            snmp_sess_perror(progname, ss);
#if HAVE_CURSES_H
            endwin();
#endif
            exit(1);
        }
        if (response->errstat != SNMP_ERR_NOERROR) {
            vlp = vlp->next_variable;
            continue;
        }

        memset(&proc, 0, sizeof(proc));

        proc.hrSWRunIndex = vlp->name[base_length];

        vlp2 = response->variables;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        len = vlp2->val_len;
        proc.hrSWRunName = malloc(len+1);
        memcpy(proc.hrSWRunName, vlp2->val.string, len);
        proc.hrSWRunName[len] = '\0';

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        proc.hrSWRunID = *vlp2->val.integer;

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        len = vlp2->val_len;
        proc.hrSWRunPath = malloc(len+1);
        memcpy(proc.hrSWRunPath, vlp2->val.string, len);
        proc.hrSWRunPath[len] = '\0';

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        len = vlp2->val_len;
        proc.hrSWRunParameters = malloc(len+1);
        memcpy(proc.hrSWRunParameters, vlp2->val.string, len);
        proc.hrSWRunParameters[len] = '\0';

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        proc.hrSWRunType = *vlp2->val.integer;

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        proc.hrSWRunStatus = *vlp2->val.integer;

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        proc.hrSWRunPerfCPU = *vlp2->val.integer;

        vlp2 = vlp2->next_variable;
        if (vlp2->type == SNMP_NOSUCHINSTANCE ||
            vlp2->type == SNMP_NOSUCHOBJECT) goto next;
        proc.hrSWRunPerfMem = *vlp2->val.integer;

        count++;
        procs = realloc(procs, count*sizeof(procs[0]));
        procs[count-1] = proc;

        snmp_free_pdu(response);
        vlp2 = vlp;
        vlp = vlp->next_variable;
        free(vlp2);
        continue;

next:
        if (proc.hrSWRunName) free(proc.hrSWRunName);
        if (proc.hrSWRunPath) free(proc.hrSWRunPath);
        if (proc.hrSWRunParameters) free(proc.hrSWRunParameters);
        snmp_free_pdu(response);
        vlp2 = vlp;
        vlp = vlp->next_variable;
        free(vlp2);
    }
    *fproc = procs;
    return count;
}


void free_perf(struct hrSWRunTable *procs, int nproc)
{
    int i;
    for (i = 0; i < nproc; i++) {
        struct hrSWRunTable *proc = procs+i;
        if (proc->hrSWRunName) free(proc->hrSWRunName);
        if (proc->hrSWRunPath) free(proc->hrSWRunPath);
        if (proc->hrSWRunParameters) free(proc->hrSWRunParameters);
    }
    free(procs);
}


int collect_cpu(netsnmp_session *ss, struct cpuStats *cpu)
{
    netsnmp_pdu    *pdu;
    netsnmp_pdu    *response;
    int status;
    int ret = 0;

    pdu = snmp_pdu_create(SNMP_MSG_GET);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawUser.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawNice.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawSystem.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawIdle.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawWait.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawKernel.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawInterrupt.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawSoftIRQ.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawSteal.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawGuest.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:ssCpuRawGuestNice.0", NULL, 0);

    status = snmp_synch_response(ss, pdu, &response);
    memset(cpu, 0, sizeof(*cpu));
    if (status != STAT_SUCCESS || !response ||
            response->errstat != SNMP_ERR_NOERROR) {
        goto out;
    }
    else {
        netsnmp_variable_list *vlp = response->variables;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->user = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->nice = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->system = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->idle = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->wait = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->kernel = *vlp->val.integer;
        vlp = vlp->next_variable;

        ret = 1;

        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->intr = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->softintr = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->steal = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->guest = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        cpu->guestnice = *vlp->val.integer;
    }
out:
    if (response) snmp_free_pdu(response);
    return ret;
}


int collect_mem(netsnmp_session *ss, struct memStats *mem)
{
    netsnmp_pdu    *pdu;
    netsnmp_pdu    *response;
    int status;
    int ret = 0;

    pdu = snmp_pdu_create(SNMP_MSG_GET);
    add(pdu, "UCD-SNMP-MIB:memTotalSwap.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memAvailSwap.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memTotalReal.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memAvailReal.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memShared.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memBuffer.0", NULL, 0);
    add(pdu, "UCD-SNMP-MIB:memCached.0", NULL, 0);

    status = snmp_synch_response(ss, pdu, &response);
    memset(mem, 0, sizeof(*mem));
    if (status != STAT_SUCCESS || !response ||
            response->errstat != SNMP_ERR_NOERROR) {
        goto out;
    }
    else {
        netsnmp_variable_list *vlp = response->variables;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->totalSwap = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->availSwap = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->totalReal = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->availReal = *vlp->val.integer;
        vlp = vlp->next_variable;

        ret = 1;

        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->shared = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->buffer = *vlp->val.integer;
        vlp = vlp->next_variable;
        if (vlp->type == SNMP_NOSUCHOBJECT) goto out;
        mem->cached = *vlp->val.integer;
    }
out:
    if (response) snmp_free_pdu(response);
    return ret;
}


char *format_centisec(char *buf, size_t len, unsigned long csec)
{
    long sec, min, hour, day;

    day = csec / 100 / 3600 / 24;
    csec -= day * 100 * 3600 * 24;
    hour = csec / 100 / 3600;
    csec -= hour * 100 * 3600;
    min = csec / 100 / 60;
    csec -= min * 100 * 60;
    sec = csec / 100;
    csec -= sec * 100;

    if (day)
        snprintf(buf, len, "%ldd%02ld:%02ld:%02ld.%02lu", day, hour, min, sec, csec);
    else if (hour)
        snprintf(buf, len, "%ld:%02ld:%02ld.%02lu", hour, min, sec, csec);
    else if (min)
        snprintf(buf, len, "%ld:%02ld.%02lu", min, sec, csec);
    else
        snprintf(buf, len, "%ld.%02lu", sec, csec);
    return buf;
}


char *format_sec(char *buf, size_t len, unsigned long csec)
{
    long sec, min, hour, day;

    day = csec / 100 / 3600 / 24;
    csec -= day * 100 * 3600 * 24;
    hour = csec / 100 / 3600;
    csec -= hour * 100 * 3600;
    min = csec / 100 / 60;
    csec -= min * 100 * 60;
    sec = csec / 100;
    csec -= sec * 100;

    if (day)
        snprintf(buf, len, "%ldd%02ld:%02ld:%02ld", day, hour, min, sec);
    else if (hour)
        snprintf(buf, len, "%ld:%02ld:%02ld.%02lu", hour, min, sec, csec);
    else if (min)
        snprintf(buf, len, "%ld:%02ld.%02lu", min, sec, csec);
    else
        snprintf(buf, len, "%ld.%02lu", sec, csec);
    return buf;
}


char *format_humanmem(char *buf, size_t len, unsigned long mem)
{
    if (mem >= 1024*1024)
        snprintf(buf, len, "%4.2fGB", (float)mem/(1024*1024));
    else if (mem >= 1024)
        snprintf(buf, len, "%4.2fMB", (float)mem/1024);
    else
        snprintf(buf, len, "%4.2fkB", (float)mem);
    return buf;
}


int cpucomp(const void *v1, const void *v2)
{
    const struct hrSWRunTable *p1 = v1, *p2 = v2;
    return p2->hrSWRunPerfCPUInc - p1->hrSWRunPerfCPUInc;
}


int memcomp(const void *v1, const void *v2)
{
    const struct hrSWRunTable *p1 = v1, *p2 = v2;
    return p2->hrSWRunPerfMem - p1->hrSWRunPerfMem;
}


int totcomp(const void *v1, const void *v2)
{
    const struct hrSWRunTable *p1 = v1, *p2 = v2;
    return p2->hrSWRunPerfCPU - p1->hrSWRunPerfCPU;
}


int pidcomp(const void *v1, const void *v2)
{
    const struct hrSWRunTable *p1 = v1, *p2 = v2;
    return p1->hrSWRunIndex - p2->hrSWRunIndex;
}


int
snmpps(int argc, char *argv[])
{
    netsnmp_session session, *ss;
    int             arg;
    struct hrSWRunTable *procs;
    int             count, pinx = 0;

    /*
     * get the common command line arguments 
     */
    switch (arg = snmp_parse_args(argc, argv, &session, "C:", optProc)) {
    case NETSNMP_PARSE_ARGS_ERROR:
        exit(1);
    case NETSNMP_PARSE_ARGS_SUCCESS_EXIT:
        exit(0);
    case NETSNMP_PARSE_ARGS_ERROR_USAGE:
        usage();
        exit(1);
    default:
        break;
    }

    if (arg != argc) {
        fprintf(stderr, "snmpps: extra argument: %s\n", argv[arg]);
        exit(1);
    }

    SOCK_STARTUP;

    /*
     * Open an SNMP session.
     */
    ss = snmp_open(&session);
    if (ss == NULL) {
        /*
         * diagnose snmp_open errors with the input netsnmp_session pointer 
         */
        snmp_sess_perror("snmpps", &session);
        SOCK_CLEANUP;
        exit(1);
    }

    count = collect_perf(ss, &procs);
    if (count == 0) {
        fprintf(stderr, "snmpps: no processes found\n");
        exit(1);
    }

    switch (topsort) {
    case 'm':
        qsort(procs, count, sizeof(procs[0]), memcomp);
        break;
    case 't':
        qsort(procs, count, sizeof(procs[0]), totcomp);
        break;
    }

    printf("%7s %4s %6s %10s %11s %-10s\n",
        "Index", "Type", "Status", "Memory", "CPU", "Command");

    while (pinx < count) {
        struct hrSWRunTable *proc = procs+pinx;
        const char *hr_status, *hr_type;
        char b1[15], b2[20];

        switch (proc->hrSWRunType) {
        case 1: hr_type = "Unkn"; break;
        case 2: hr_type = "Os"; break;
        case 3: hr_type = "Drvr"; break;
        case 4: hr_type = "Appl"; break;
        default: hr_type = "?"; break;
        }

        switch (proc->hrSWRunStatus) {
        case 1: hr_status = "Run"; break;
        case 2: hr_status = "Wait"; break;
        case 3: hr_status = "Event"; break;
        case 4: hr_status = "Inval"; break;
        default: hr_status = "?"; break;
        }

        printf("%7lu %4s %6s %10s %11.11s %s %s\n",
               proc->hrSWRunIndex,
               hr_type,
               hr_status,
               format_humanmem(b1, sizeof b1, proc->hrSWRunPerfMem),
               format_centisec(b2, sizeof b2, proc->hrSWRunPerfCPU),
               command_path && proc->hrSWRunPath[0] ? proc->hrSWRunPath : proc->hrSWRunName,
               command_args ? proc->hrSWRunParameters : "");

        pinx++;
    }

    snmp_close(ss);
    SOCK_CLEANUP;
    return 0;
}


#if HAVE_CURSES_H
static void endtop(int sig)
{
    endwin();
    exit(1);
}


int snmptop(int argc, char **argv)
{
    netsnmp_session session, *ss;
    int             arg;
    struct hrSWRunTable *oproc;
    int             ocount = 0;
    int             show_idle = 1;
    int             show_os = 1;
    char            ch;
    struct cpuStats oldCpu;
    struct memStats mem;
    int             has_cpu, has_mem; 

    /*
     * get the common command line arguments 
     */
    switch (arg = snmp_parse_args(argc, argv, &session, "C:", optProc)) {
    case NETSNMP_PARSE_ARGS_ERROR:
        exit(1);
    case NETSNMP_PARSE_ARGS_SUCCESS_EXIT:
        exit(0);
    case NETSNMP_PARSE_ARGS_ERROR_USAGE:
        usage();
        exit(1);
    default:
        break;
    }

    if (arg != argc) {
        fprintf(stderr, "snmptop: extra argument: %s\n", argv[arg]);
        exit(1);
    }

    SOCK_STARTUP;

    /*
     * Open an SNMP session.
     */
    ss = snmp_open(&session);
    if (ss == NULL) {
        /*
         * diagnose snmp_open errors with the input netsnmp_session pointer 
         */
        snmp_sess_perror("snmptop", &session);
        SOCK_CLEANUP;
        exit(1);
    }

    ocount = collect_perf(ss, &oproc);
    if (ocount == 0) {
        fprintf(stderr, "snmptop: no processes found\n");
        exit(1);
    }

    collect_cpu(ss, &oldCpu);

    signal(SIGINT, endtop);
    initscr();
    cbreak();
    noecho();
    nonl();
    halfdelay(50);

    while ((ch = getch()) != 'q') {
        int ncount;
        struct hrSWRunTable *nproc;
        int oinx = 0, ninx = 0, line = 0;
        netsnmp_pdu    *pdu;
        netsnmp_pdu    *response = NULL;
        int status;
        time_t clock;
        struct tm *ptm;
        char uptime[40];
        char timestr[40];
        char b1[15], b2[15], b3[15], b4[15];
        struct cpuStats newCpu;

        if (ch == 'c' || ch == 'm' || ch == 'n' || ch == 't') topsort = ch;
        if (ch == 'i') show_idle = !show_idle;
        if (ch == 'o') show_os = !show_os;
        if (ch == 'a') command_args = !command_args;
        if (ch == 'p') command_path = !command_path;

        ncount = collect_perf(ss, &nproc);

        while (oinx < ocount && ninx < ncount) {
            if (oproc[oinx].hrSWRunIndex == nproc[ninx].hrSWRunIndex) {
                nproc[ninx].hrSWRunPerfCPUInc = nproc[ninx].hrSWRunPerfCPU-oproc[oinx].hrSWRunPerfCPU;
                ninx++;
                oinx++;
            }
            else if (nproc[oinx].hrSWRunIndex < oproc[ninx].hrSWRunIndex)
                oinx++;
            else {
                nproc[ninx].hrSWRunPerfCPUInc = nproc[ninx].hrSWRunPerfCPU;
                ninx++;
            }
        }
        while (ninx < ncount) {
            nproc[ninx].hrSWRunPerfCPUInc = nproc[ninx].hrSWRunPerfCPU;
            ninx++;
        }

        switch (topsort) {
        case 'c':
            qsort(nproc, ncount, sizeof(nproc[0]), cpucomp);
            break;
        case 'm':
            qsort(nproc, ncount, sizeof(nproc[0]), memcomp);
            break;
        case 't':
            qsort(nproc, ncount, sizeof(nproc[0]), totcomp);
            break;
        }

        has_cpu = collect_cpu(ss, &newCpu);
        has_mem = collect_mem(ss, &mem);

        pdu = snmp_pdu_create(SNMP_MSG_GET);
        add(pdu, "HOST-RESOURCES-MIB:hrSystemUptime.0", NULL, 0);
        status = snmp_synch_response(ss, pdu, &response);
        if (status != STAT_SUCCESS || !response ||
                response->errstat != SNMP_ERR_NOERROR) {
            uptime[0] = '\0';
        }
        else {
            netsnmp_variable_list *vlp = response->variables;
            if (vlp->type == SNMP_NOSUCHINSTANCE) abort();
            uptime_string_n(*vlp->val.integer, uptime, sizeof(uptime));
        }
        snmp_free_pdu(response);

        clock = time(NULL);
        ptm = localtime(&clock);
        strftime(timestr, sizeof(timestr), "%H:%M:%S", ptm);

        clear();
        move(0, 0);
        printw("%s %s%s", session.peername, uptime[0] ? "up " : "", uptime);
        move(0, COLS-strlen(timestr)-1);
        printw("%s", timestr);
        if (has_cpu) {
            struct cpuStats deltaCpu;
            u_long sumCpu;

            deltaCpu.user = newCpu.user - oldCpu.user;
            deltaCpu.nice = newCpu.nice - oldCpu.nice;
            deltaCpu.system = newCpu.system - oldCpu.system;
            deltaCpu.idle = newCpu.idle - oldCpu.idle;
            deltaCpu.wait = newCpu.wait - oldCpu.wait;
            deltaCpu.kernel = newCpu.kernel - oldCpu.kernel;
            deltaCpu.intr = newCpu.intr - oldCpu.intr;
            deltaCpu.softintr = newCpu.softintr - oldCpu.softintr;
            deltaCpu.steal = newCpu.steal - oldCpu.steal;
            deltaCpu.guest = newCpu.guest - oldCpu.guest;
            deltaCpu.guestnice = newCpu.guestnice - oldCpu.guestnice;
            oldCpu = newCpu;
            sumCpu = deltaCpu.user + deltaCpu.nice
                + deltaCpu.system + deltaCpu.idle
                + deltaCpu.wait + deltaCpu.kernel + deltaCpu.steal
                + deltaCpu.intr + deltaCpu.softintr
                + deltaCpu.guest + deltaCpu.guestnice;

            printw("\nCPU%%: %4.1fUs %4.1fSy %4.1fId %3.1fWa %3.1fNi %3.1fKe %3.1fHi %3.1fSi %3.1fSt %3.1fGu %3.1fGN",
                (float)deltaCpu.user*100/sumCpu,
                (float)deltaCpu.system*100/sumCpu,
                (float)deltaCpu.idle*100/sumCpu,
                (float)deltaCpu.wait*100/sumCpu,
                (float)deltaCpu.nice*100/sumCpu,
                (float)deltaCpu.kernel*100/sumCpu,
                (float)deltaCpu.intr*100/sumCpu,
                (float)deltaCpu.softintr*100/sumCpu,
                (float)deltaCpu.steal*100/sumCpu,
                (float)deltaCpu.guest*100/sumCpu,
                (float)deltaCpu.guestnice*100/sumCpu);
            line++;
        }

        if (has_mem) {
            printw("\nMem:  %10s Total %10s Used %10s Free %10s Buffer",
                format_humanmem(b1, sizeof b1, mem.totalReal),
                format_humanmem(b2, sizeof b2, mem.totalReal-mem.availReal),
                format_humanmem(b3, sizeof b3, mem.availReal),
                format_humanmem(b4, sizeof b4, mem.buffer));
            line++;
            printw("\nSwap: %10s Total %10s Used %10s Free %10s Cached",
                format_humanmem(b1, sizeof b1, mem.totalSwap),
                format_humanmem(b2, sizeof b2, mem.totalSwap-mem.availSwap),
                format_humanmem(b3, sizeof b3, mem.availSwap),
                format_humanmem(b4, sizeof b4, mem.cached));
            line++;
        }

        printw("\n%7s %4s %6s %10s %11s %5s %-10s",
            "Index", "Type", "Status", "Memory", "Total CPU", "%CPU", "Command");
        line++;
        ninx = 0;
        while (line < LINES && ninx < ncount) {
            struct hrSWRunTable *proc = nproc+ninx;
            const char *hr_status, *hr_type;

            ninx++;
            if (proc->hrSWRunPerfCPUInc == 0 && !show_idle)
                continue;
            if (proc->hrSWRunType != 4 && !show_os)
                continue;

            line++;

            switch (proc->hrSWRunType) {
            case 1: hr_type = "Unkn"; break;
            case 2: hr_type = "Os"; break;
            case 3: hr_type = "Drvr"; break;
            case 4: hr_type = "Appl"; break;
            default: hr_type = "?"; break;
            }

            switch (proc->hrSWRunStatus) {
            case 1: hr_status = "Run"; break;
            case 2: hr_status = "Wait"; break;
            case 3: hr_status = "Event"; break;
            case 4: hr_status = "Inval"; break;
            default: hr_status = "?"; break;
            }

            printw("\n%7lu %4s %6s %10s %11s %5.1f %s %s",
                   proc->hrSWRunIndex,
                   hr_type,
                   hr_status,
                   format_humanmem(b1, sizeof b1, proc->hrSWRunPerfMem),
                   format_sec(b2,sizeof b2, proc->hrSWRunPerfCPU),
                   (float)proc->hrSWRunPerfCPUInc/5,
                   command_path && proc->hrSWRunPath[0] ? proc->hrSWRunPath : proc->hrSWRunName,
                   command_args ? proc->hrSWRunParameters : "");
        }
        refresh();

        qsort(nproc, ncount, sizeof(nproc[0]), pidcomp);
        free_perf(oproc, ocount);
        oproc = nproc;
        ocount = ncount;
    }
    endwin();

    free_perf(oproc, ocount);

    snmp_close(ss);
    SOCK_CLEANUP;
    return 0;
}
#else
int snmptop(int argc, char **argv)
{
    fprintf(stderr, "%s: curses not detected during configure\n", progname);
    return 1;
}
#endif


int main(int argc, char **argv)
{
    progname = strrchr(argv[0], '/');
    if (progname) progname++;
    else progname = argv[0];
    if (strncmp(progname, "lt-", 3) == 0) progname += 3;

    if (strcmp(progname, "snmpps") == 0) return snmpps(argc, argv);
    if (strcmp(progname, "snmptop") == 0) return snmptop(argc, argv);
    fprintf(stderr, "%s: unknown prognam name\n", progname);
    exit(1);
}
