/**********************************************************************/
/*   TimeMon (c)  1994  Helmut Maierhofer			      */
/*   KDE-ified M. Maierhofer 1998                                     */
/**********************************************************************/

/*
 * sample.cc
 *
 * Definitions for the system dependent sampling class.
 */

#include <config.h>

#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <fstream.h>
#include <stdio.h>

#ifdef __osf__
#include <sys/table.h>
#elif defined(__sun__)
#include <kstat.h>
#include <sys/sysinfo.h>
#include <sys/stat.h>
#include <sys/swap.h>
#endif

#include <qwidget.h>
#include <klocale.h>
#include <kmessagebox.h>

#include "timemon.h"
#include "sample.h"

// -- global definitions -------------------------------------------------

#if defined(__osf__) || defined(__sun__)
extern "C" int getpagesize();	// argh, have to define prototype!
#endif

#ifdef linux
// -- global constants ---------------------------------------------------
static const char *STAT_NAME = "stat";
static const char *MEMINFO_NAME = "meminfo";
static const char *MTAB_NAME = "/etc/mtab";
#endif

// -- KSample::Sample definition -----------------------------------------

// Fill sample with some default values (e.g. used in preview widget
// in configuration)
void KSample::Sample::fill(unsigned scale)
{
  user = scale * 40; user /= 100;
  nice = scale * 25; user /= 100;
  kernel = scale * 15; kernel /= 100;
  cpus = 1;
  buffers = scale * 20; buffers /= 100;
  used = scale * 30; used /= 100;
  cached = scale * 20; cached /= 100;
  sused = scale * 25; sused /= 100;
  pout = scale * 10; pout /= 100;
  pin = scale * 20; pin /= 100;
  swout = scale * 30; swout /= 100;
  swin = scale * 15; swin /= 100;
  cswitches = scale * 75; cswitches /= 100;
}

// -- KSample definition -------------------------------------------------

// Initialise the member variables and try to open the standard files in
// the proc filesystem; for other platforms perform equivalent initialisation
KSample::KSample(KTimeMon *t, bool a, unsigned p, unsigned s, unsigned c) :
  timemon(t),
#ifdef linux
  memFD(-1), statFD(-1),
#elif defined (__sun__)
  kc(0), warned(false),
#endif
  pageScale(p), swapScale(s), cxScale(c), autoscale(a)
{
#ifdef linux

  parseMtab(proc);

  char file[512];
  snprintf(file, sizeof(file), "%s/%s", proc, MEMINFO_NAME);

  if ((memFD = open(file, O_RDONLY)) == -1) {
    KMessageBox::error(timemon,
                       i18n("Sorry, I cannot open the file `%1'.\nThe diagnostics are: %2.\n\n"
                            "I need this file to determine current memory usage.\n"
                            "Maybe your proc filesystem is non Linux-standard?").arg(file).arg(strerror(errno)));
    exit(1);
  }

  snprintf(file, sizeof(file), "%s/%s", proc, STAT_NAME);
  if ((statFD = open(file, O_RDONLY)) == -1) {
    KMessageBox::error(timemon,
                       i18n("Sorry, I cannot open the file `%s'.\nThe diagnostics are: %s.\n\n"
                            "I need this file  to determine current system info.\n"
                            "Maybe your proc filesystem is non Linux-standard?"));
    exit(1);
  }

#elif defined (__sun__)
  if ((kc = kstat_open()) == 0) {
    KMessageBox::error(timemon, i18n("Sorry, I cannot initialise the `kstat' library.\n"
                                     "This library is used for accessing kernel information.\n"
                                     "The diagnostics are: `%s'.\n\n"
                                     "Are you running Solaris at all?\n"
                                     "You may want to mail me at mueller@kde.org,\n"
                                     "I will try to figure out what went wrong.").arg(strerror(errno)));
    exit(1);
  }
#endif

#if defined(__sun__) || defined(__osf__)
  pagesPerMB = (1024*1024) / getpagesize();
  if (pagesPerMB == 0) pagesPerMB = 1; // paranoia sanity check
#endif

  readSample();
  updateSample();
}

// Get rid of the resources we acquired in the constructor.
KSample::~KSample()
{
#ifdef linux
  close(memFD);
  close(statFD);
#elif defined (__sun__)
  if (kc != 0) kstat_close(kc);
#endif
}

#ifdef linux
// Parse /etc/mtab to determine the proc filesystem mount point.
void KSample::parseMtab(char *dest)
{
    ifstream *mtab = new ifstream(MTAB_NAME);
    if (!mtab->good()) {
        KMessageBox::error(timemon, i18n("I cannot open file `%1' to determine where the proc\n"
                                         "filesystem is mounted. The diagnostics are:\n %2\n\n"
                                         "Are you running UNIX at all?!?").arg( MTAB_NAME).arg( strerror(errno)));
        exit(1);
    }

    unsigned lineno = 0;
    char line[1024];
    QString errormsg;

    for (;;) {
        lineno++;
        mtab->getline(line, sizeof(line));

        if (mtab->bad()) {
            errormsg = i18n("I cannot read file `%1' to determine where the proc\n"
                            "filesystem is mounted. The diagnostics are:\n %2").arg(MTAB_NAME).arg(strerror(errno));
            break;
        }

        if (mtab->eof()) {
            errormsg = i18n("Hmm, I could not figure out where the proc filesystem\n"
                            "is mounted (there is no entry in `%1').\n\n"
                            "I need information from the proc filesystem to determine\n"
                            "current system usage. Maybe you are not running Linux (I'm\n"
                            "afraid the proc filesystem is Linux specific)?\n\n"
                            "If you can provide help with porting KTimeMon to your\n"
                            "platform, please contact me at <mueller@kde.org>").arg(MTAB_NAME);
            break;
        }

        if (mtab->fail()) {
            errormsg = i18n("Hmm, I encountered a very long line while reading\n"
                            "information in `%1' (where \"very long\" is defined\n"
                            "as > %2). This happened at line %3.\n\n"
                            "Is %4 the mount table on your platform?\n\n")
                .arg(MTAB_NAME).arg(sizeof(line)).arg(lineno).arg(MTAB_NAME);
            break;
        }

        char *p, *m;

        if ((p = m = strchr(line, ' ')) != 0) p = strchr(m+1, ' ');
        if (p == 0 || strncmp(p+1, "proc ", 5) != 0) continue;

        *p = '\0';
        strncpy(dest, m+1, 256);
        mtab->close();
        delete mtab;
        return;
    }

    KMessageBox::error(timemon, errormsg);
    exit(1);
}
#endif

// Set the appropriate scaling parameters
void KSample::setScaling(bool a, unsigned p, unsigned s, unsigned c)
{
    autoscale = a;
    pageScale = p;
    swapScale = s;
    cxScale = c;
}

// -----------------------------------------------------------------------------
// Show a message box with the given message and terminate the application.

void KSample::fatal(const QString& msg)
{
    timemon->stop();

    KMessageBox::error(timemon, msg);
    exit(1);
}


// -----------------------------------------------------------------------------
// Show a message box with the given message and don't terminate the app ;-)

void KSample::nonfatal(const QString& msg)
{
    timemon->stop();

    KMessageBox::sorry(timemon, msg);
    timemon->cont();
}


// -----------------------------------------------------------------------------
// Read a new sample from the files or whatever resource the OS implements

void KSample::readSample()
{
    sample.cpus = 0;		// just to make sure...

#ifdef linux			// linux makes it simple: use the /proc if
    int l;
    char buffer[4096];

    lseek(memFD, 0, 0);
    if ((l = read(memFD, buffer, sizeof(buffer) - 1)) < 0)
        fatal(i18n("Sorry, I cannot read the memory usage file `%1/%2'.\n"
                   "The diagnostics are: %3").arg(proc).arg(MEMINFO_NAME).arg(strerror(errno)));

    buffer[l] = '\0';
    static struct {
        const char *name;
        unsigned long *stat;
    } memstats[] = {
        { "SwapTotal:", &sample.stotal },
        { "MemTotal:", &sample.mtotal },
        { "MemFree:", &sample.free },
        { "Buffers:", &sample.buffers },
        { "Cached:", &sample.cached },
        { "SwapFree:", &sample.sfree },
        { 0, 0 }
    };

    l = 0;
    char *p;
    while (memstats[l].name != 0) {
        p = strstr(buffer, memstats[l].name);
        if (p == 0 ||
            sscanf(p + strlen(memstats[l].name), "%lu kB", memstats[l].stat) < 1)
            fatal(i18n("Sorry, the memory usage file `%1/%2'\nseems to use a "
                       "different file format than I expect.\n\n"
                       "Maybe your version of the proc filesystem is\n"
                       "incompatible with mine. Mail me at\n"
                       "m.maierhofer@tees.ac.uk and I will try to sort this out.").arg(proc).arg(MEMINFO_NAME));
        l++;
    }

    lseek(statFD, 0, 0);
    if ((l = read(statFD, buffer, sizeof(buffer)-1)) < 0)
        fatal(i18n("Sorry, I cannot read the system usage file `%1/%2'.\n"
                   "The diagnostics are: %3").arg(proc).arg(STAT_NAME).arg(strerror(errno)));

    buffer[l] = '\0';

    bool ok = (sscanf(buffer, "cpu %lu %lu %lu %lu\n", &sample.user,
                      &sample.nice, &sample.kernel, &sample.idle) == 4);

    if (ok) {
        for (l = 0; l < MAX_CPU; l++) { // get individual stat for SMP machines
            char cpuname[10];
            sprintf(cpuname, "cpu%d", l);

            if ((p = strstr(buffer, cpuname)) == NULL) break;

            unsigned long u, n, k, i;
            ok = sscanf(p, "cpu%*d %lu %lu %lu %lu\n", &u, &n, &k, &i);
            if (!ok) break;

            sample.smptotal[l] = u+n+k+i;
            sample.smpbusy[l] = sample.smptotal[l] - i;
        }
    }
    sample.cpus = l;

    if (ok) {
        if ((p = strstr(buffer, "page")) == NULL) ok = false;
        else ok = (sscanf(p, "page %lu %lu\n", &sample.pin, &sample.pout) == 2);
    }

    if (ok) {
        if ((p = strstr(buffer, "swap")) == NULL) ok = false;
        else ok = (sscanf(p, "swap %lu %lu\n", &sample.swin, &sample.swout) == 2);
    }

    if (ok) {
        if ((p = strstr(buffer, "ctxt")) == NULL) ok = false;
        else ok = (sscanf(p, "ctxt %lu\n", &sample.cswitches) == 1);
    }

    if (!ok)
        fatal(i18n("Sorry, the system usage file `%1/%2'\n"
                   "seems to use a different file format than I expect.\n\n"
                   "Maybe your version of the proc filesystem is\n"
                   "incompatible with mine. Mail me at\n"
                   "mueller@kde.org and I will try to sort this out.").arg(proc).arg(STAT_NAME));

#elif defined(__osf__)		// in OSF/2, we can use table()

    QString msg = i18n("Sorry, I cannot get system information.\n\n"
                       "The table(2) system call returned an error\n"
                       "for table %1.\n\n"
                       "You may want to mail me at mueller@kde.org, "
                       "I will try to figure out what went wrong.");

    struct tbl_sysinfo sysinfo;
    if (table(TBL_SYSINFO, 0, &sysinfo, 1, sizeof(sysinfo)) != 1)
        fatal(fmt.arg("TBL_SYSINFO"));

    sample.user = sysinfo.si_user;
    sample.nice = sysinfo.si_nice;
    sample.kernel = sysinfo.si_sys;
    sample.idle = sysinfo.si_idle + sysinfo.wait;

    struct tbl_vmstats vmstats;
    if (table(TBL_VMSTATS, 0, &vmstats, 1, sizeof(vmstats)) != 1)
        fatal(fmt.arg("TBL_VMSTATS"));

    sample.mtotal = vmstats.free_count + vmstats.active_count +
        vmstats.inactive_count + vmstats.wire_count;
    sample.free = vmstats.free_count;
    sample.buffers = vmstats.inactive_count; // pages not used for some time
    sample.cached = vmstats.wire_count; // kernel/driver memory

    struct tbl_swapinfo swapinfo;
    if (table(TBL_SWAPINFO, -1, &swapinfo, 1, sizeof(swapinfo)) != 1)
        fatal(fmt.arg("TBL_SWAPINFO"));

    sample.stotal = swapinfo.size;
    sample.sfree = swapinfo.free;

    struct tbl_paging paging;
    if (table(TBL_PAGING, 0, &paging, 1, sizeof(paging)) != 1)
        fatal(fmt.arg("TBL_PAGING"));

    sample.pin = sample.swin = paging.v_pgpgin; // MM no separate swap
    sample.pout = sample.swout = paging.v_pgout;

    struct tbl_intr intr;
    if (table(TBL_INTR, 0, &intr, 1, sizeof(intr)) != 1)
        fatal(fmt.arg("TBL_INTR"));

    sample.cswitches = intr.in_context;

#elif defined(__sun__)
    kstat_t *ksp;

    sample.cpus = 0;
    for (ksp = kc->kc_chain; ksp != 0; ksp = ksp->ks_next) {
        if (strncmp(ksp->ks_name, "cpu_stat", 8) != 0) continue;
        sample.cpus++;
    }

    if (sample.cpus == 0)
        fatal(i18n("Sorry, I cannot find any entries for CPU statistics\n"
                   "in the `kstat' library. Are you running a non-standard\n"
                   "version of Solaris?\n\n"
                   "Please contact me at mueller@kde.org and I will try to sort this out."));

    sample.user = sample.nice = sample.kernel = sample.idle = 0;
    sample.pin = sample.pout = sample.swin = sample.swout = 0;
    sample.cswitches = 0;
    sample.stotal = sample.sfree = 0;

    int cpus = 0;
    for (ksp = kc->kc_chain; ksp != 0; ksp = ksp->ks_next) {
        if (strncmp(ksp->ks_name, "cpu_stat", 8) != 0) continue;
        cpus++;

        cpu_stat_t cstat;
        if (kstat_read(kc, ksp, 0) == -1 || // update from kernel
            kstat_read(kc, ksp, &cstat) == -1) // and read into buffer
            fatal(i18n("Sorry, I cannot read the CPU statistics entry\n"
                       "from the `kstat' library. The diagnostics are `%1'.\n\n"
                       "You might want to contact me at\n"
                       "mueller@kde.org and I will try to sort this out.").arg(strerror(errno)));

        // fields are: idle user kernel iowait
        sample.user += cstat.cpu_sysinfo.cpu[1] / sample.cpus;
        sample.nice += cstat.cpu_sysinfo.cpu[3] / sample.cpus;
        sample.kernel += cstat.cpu_sysinfo.cpu[2] / sample.cpus;
        sample.idle += cstat.cpu_sysinfo.cpu[0] / sample.cpus;

        sample.pin += cstat.cpu_vminfo.pgin / sample.cpus;
        sample.pout += cstat.cpu_vminfo.pgout / sample.cpus;
        sample.swin += cstat.cpu_vminfo.swapin / sample.cpus;
        sample.swout += cstat.cpu_vminfo.swapout / sample.cpus;

        sample.cswitches += cstat.cpu_sysinfo.pswitch / sample.cpus;
    }

    if (cpus != sample.cpus)
        fatal(i18n("Huhh, now this is very strange.\n"
                   "The number of CPUs seems to have changed at\n"
                   "very short notice. Or the `kstat' library returns\n"
                   "inconsistent results (%1 vs. %2 CPUs).\n"
                   "Please contact me at mueller@kde.org and I will try to sort this out.").arg(sample.cpus).arg(cpus));

    // availrmem = pages of core for user-proc ( == physmem - kernelmem)
    // freemem = no of free pages
    // physmem == total mem in 4KB blocks

    errno = 0;
    if ((ksp = kstat_lookup(kc, "unix", -1, "system_pages")) == 0 ||
        kstat_read(kc, ksp, 0) == -1)
        fatal(i18n("Sorry, I cannot read the memory statistics entry\n"
                   "from the `kstat' library. The diagnostics are `%1' and ksp is %2.\n\n"
                   "You might want to contact me at\n"
                   "mueller@kde.org and I will try to sort this out.").arg(strerror(errno)).arg(ksp));

    int i;
    unsigned long physmem = 0, freemem = 0, availrmem = 0;

    kstat_named_t *kn = (kstat_named_t *)ksp->ks_data;
    for (i = 0; i < (int) ksp->ks_ndata; i++) {
        if (strcmp(kn->name, "physmem") == 0) physmem = kn->value.ul;
        else if (strcmp(kn->name, "freemem") == 0) freemem = kn->value.ul;
        else if (strcmp(kn->name, "availrmem") == 0) availrmem = kn->value.ul;
        kn++;
    }

    if (physmem == 0)		// sanity check, this should always be > 0
        fatal(i18n("Uh uh, there seems to be a problem with my handling\n"
                   "of the `kstat' library: I determined 0 physical memory!\n"
                   "(Free memory is %1, available memory is %2.\n\n"
                   "Please contact me at mueller@kde.org and I will try to sort this out.").arg(freemem).arg(availrmem));

    sample.mtotal = physmem;
    sample.free = freemem;
    sample.buffers = 0;
    sample.cached = physmem - availrmem; // memory used by the kernel

    int swapentries;
    if ((swapentries = swapctl(SC_GETNSWP, 0)) == -1)
        fatal(i18n("Sorry, cannot determine the number of\n"
                   "swap spaces. The diagnostics are `%1'.\n\n"
                   "Please contact me at mueller@kde.org and I will try to sort this out.").arg(strerror(errno)));

    if (swapentries != 0) {
				// 2* to get some space for padding??
        swaptbl_t *stbl = (swaptbl_t *) malloc(2*sizeof(int) + swapentries *
                                               sizeof(struct swapent));
        if (stbl == 0)
            fatal(i18n("Sorry, I ran out of memory while\n"
                       "trying to determine the swap usage.\n"
                       "I tried to allocate %1 bytes of memory (2 * %2 + %3 * %4).\n\n"
                       "Please contact me at mueller@kde.org and I will try to sort this out.")
                  .arg(2 * sizeof(int) + swapentries * sizeof(struct swapent))
                  .arg(sizeof(int)).arg(swapentries).arg(sizeof(struct swapent)));

        char path[1024];
        stbl->swt_n = swapentries;
        for (i = 0; i < swapentries; i++) stbl->swt_ent[i].ste_path = path;

        if ((swapentries = swapctl(SC_LIST, stbl)) == -1)
            fatal(i18n("Sorry, I cannot determine the swap usage.\n"
                       "The diagnostics are `%1'.\n\n"
                       "Please contact me at mueller@kde.org and I will try to sort this out.").arg(strerror(errno)));


        if (!warned && swapentries != stbl->swt_n) {
            warned = true;
            nonfatal(i18n("Strange, I requested information for\n"
                          "%1 swap spaces, but only got %2 swap entries back.\n"
                          "I am a bit confused now, but I will try to continue.\n\n"
                          "Please contact me at mueller@kde.org and I will try to sort this out.").arg(stbl->swt_n).arg(swapentries));
        }

        for (i = 0; i < swapentries; i++) {
            sample.stotal += stbl->swt_ent[i].ste_pages;
            sample.sfree += stbl->swt_ent[i].ste_free;
        }

        free(stbl);
    }

#endif

    sample.cputotal = sample.user + sample.nice + sample.kernel + sample.idle;
    sample.used = sample.mtotal - sample.free - sample.buffers - sample.cached;
    sample.sused = sample.stotal - sample.sfree;
}

// Read a new sample after copying the old one.
void KSample::updateSample()
{
    oldSample = sample;
    readSample();
}

// Convert v to a value representing megabytes.
inline void KSample::makeMBytes(unsigned long &v)
{
#ifdef linux
    v /= 1024;			// can it be simpler ;-)
#elif defined (__osf__) || defined(__sun__)
    v /= pagesPerMB;
#endif
}

// Return unscaled sample
KSample::Sample KSample::getRawSample()
{
    Sample diff = sample;

    diff.cputotal -= oldSample.cputotal;

    diff.user -= oldSample.user;
    diff.nice -= oldSample.nice;
    diff.kernel -= oldSample.kernel;

    for (int i = 0; i < diff.cpus; i++) {
        diff.smptotal[i] -= oldSample.smptotal[i];
        diff.smpbusy[i] -= oldSample.smpbusy[i];
    }

    diff.pin -= oldSample.pin;
    diff.pout -= oldSample.pout;

    diff.swin -= oldSample.swin;
    diff.swout -= oldSample.swout;

    diff.cswitches -= oldSample.cswitches;

    return diff;
}

// Better scaling, round according to first decimal
inline unsigned long KSample::doScale(unsigned long value, unsigned scale1,
				      unsigned long scale2)
{
    if (scale2 == 0) scale2 = (unsigned long)~0; // avoid SEGVs

    unsigned long v = value * scale1 * 10;
    v /= scale2;
    unsigned r = v % 10;
    v /= 10;
    if (r > 4) v++;
    return v;
}

// Provide the difference from the last to the current sample, scale it
// and return it.
KSample::Sample KSample::getSample(unsigned scale)
{
    Sample s = getRawSample();

    s.user = doScale(s.user, scale, s.cputotal);
    s.nice = doScale(s.nice, scale, s.cputotal);
    s.kernel = doScale(s.kernel, scale, s.cputotal);

    for (int i = 0; i < s.cpus; i++)
        s.smpbusy[i] = doScale(s.smpbusy[i], scale, s.smptotal[i]);

    s.cached = doScale(s.cached, scale, s.mtotal);
    s.buffers = doScale(s.buffers, scale, s.mtotal);
    s.used = doScale(s.used, scale, s.mtotal);
    makeMBytes(s.mtotal);

    s.sused = doScale(s.sused, scale, s.stotal);
    makeMBytes(s.stotal);

    s.pin *= scale;
    s.pout *= scale;
    unsigned page = (s.pin + s.pout) / 2;

    if (autoscale && page > 0 &&		// autoscale
        (page / pageScale > scale / 2 || page / pageScale < 1)) {
        pageScale = page / (scale/4);
    }
    s.pin = doScale(s.pin, 1, pageScale);
    s.pout = doScale(s.pout, 1, pageScale);

    s.swin *= scale;
    s.swout *= scale;
    unsigned swap = (s.swin + s.swout) / 2;

    if (autoscale && swap > 0 &&		// autoscale
        (swap / swapScale > scale / 2 || swap / swapScale < 1)) {
        swapScale = swap / (scale/4);
    }
    s.swin = doScale(s.swin, 1, swapScale);
    s.swout = doScale(s.swout, 1, swapScale);

    s.cswitches *= scale;
    if (autoscale && s.cswitches > 0 &&	// auto scale
        (s.cswitches / cxScale > scale || s.cswitches / cxScale < 2)) {
        cxScale = s.cswitches/(scale/2);
    }
    s.cswitches = doScale(s.cswitches, 1, cxScale);

    return s;
}

