/*
    KSysGuard, the KDE System Guard

    Copyright (c) 1999, 2000 Chris Schlaeger <cs@kde.org>

    This program is free software; you can redistribute it and/or
    modify it under the terms of version 2 of the GNU General Public
    License as published by the Free Software Foundation.

    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "Command.h"
#include "ksysguardd.h"

#include "stat.h"

typedef struct
{
  /* A CPU can be loaded with user processes, reniced processes and
   * system processes. Unused processing time is called idle load.
   * These variable store the percentage of each load type. */
  int userLoad;
  int niceLoad;
  int sysLoad;
  int idleLoad;

  /* To calculate the loads we need to remember the tick values for each
   * load type. */
  unsigned long userTicks;
  unsigned long niceTicks;
  unsigned long sysTicks;
  unsigned long idleTicks;
} CPULoadInfo;

typedef struct
{
  unsigned long delta;
  unsigned long old;
} DiskLoadSample;

typedef struct
{
  /* 5 types of samples are taken:
       total, rio, wio, rBlk, wBlk */
  DiskLoadSample s[ 5 ];
} DiskLoadInfo;

typedef struct DiskIOInfo
{
  int major;
  int minor;
  int alive;
  DiskLoadSample total;
  DiskLoadSample rio;
  DiskLoadSample wio;
  DiskLoadSample rblk;
  DiskLoadSample wblk;
  struct DiskIOInfo* next;
} DiskIOInfo;

#define STATBUFSIZE (32 * 1024)

static char StatBuf[ STATBUFSIZE ];
static char VmStatBuf[ STATBUFSIZE ];
static char IOStatBuf[ STATBUFSIZE ];	/* Buffer for /proc/diskstats */
static int Dirty = 0;

/* We have observed deviations of up to 5% in the accuracy of the timer
 * interrupts. So we try to measure the interrupt interval and use this
 * value to calculate timing dependant values. */
static float timeInterval = 0;
static struct timeval lastSampling;
static struct timeval currSampling;
static struct SensorModul* StatSM;

static CPULoadInfo CPULoad;
static CPULoadInfo* SMPLoad = 0;
static unsigned CPUCount = 0;
static DiskLoadInfo* DiskLoad = 0;
static unsigned DiskCount = 0;
static DiskIOInfo* DiskIO = 0;
static unsigned long PageIn = 0;
static unsigned long OldPageIn = 0;
static unsigned long PageOut = 0;
static unsigned long OldPageOut = 0;
static unsigned long Ctxt = 0;
static unsigned long OldCtxt = 0;
static unsigned int NumOfInts = 0;
static unsigned long* OldIntr = 0;
static unsigned long* Intr = 0;

static int initStatDisk( char* tag, char* buf, const char* label, const char* shortLabel,
                         int idx, cmdExecutor ex, cmdExecutor iq );
static void updateCPULoad( const char* line, CPULoadInfo* load );
static int processDisk( char* tag, char* buf, const char* label, int idx );
static void processStat( void );
static int processDiskIO( const char* buf );
static int process26DiskIO( const char* buf );
static void cleanupDiskList( void );

static int initStatDisk( char* tag, char* buf, const char* label,
                         const char* shortLabel, int idx, cmdExecutor ex, cmdExecutor iq )
{
  char sensorName[ 128 ];

  gettimeofday( &lastSampling, 0 );

  if ( strcmp( label, tag ) == 0 ) {
    unsigned int i;
    buf = buf + strlen( label ) + 1;

    for ( i = 0; i < DiskCount; ++i ) {
      sscanf( buf, "%lu", &DiskLoad[ i ].s[ idx ].old );
      while ( *buf && isblank( *buf++ ) );
      while ( *buf && isdigit( *buf++ ) );
      sprintf( sensorName, "disk/disk%d/%s", i, shortLabel );
      registerMonitor( sensorName, "integer", ex, iq, StatSM );
    }

    return 1;
  }

  return 0;
}

static void updateCPULoad( const char* line, CPULoadInfo* load )
{
  unsigned long currUserTicks, currSysTicks, currNiceTicks, currIdleTicks;
  unsigned long totalTicks;

  sscanf( line, "%*s %lu %lu %lu %lu", &currUserTicks, &currNiceTicks,
          &currSysTicks, &currIdleTicks );

  totalTicks = ( currUserTicks - load->userTicks ) +
               ( currSysTicks - load->sysTicks ) +
               ( currNiceTicks - load->niceTicks ) +
               ( currIdleTicks - load->idleTicks );

  if ( totalTicks > 10 ) {
    load->userLoad = ( 100 * ( currUserTicks - load->userTicks ) ) / totalTicks;
    load->sysLoad = ( 100 * ( currSysTicks - load->sysTicks ) ) / totalTicks;
    load->niceLoad = ( 100 * ( currNiceTicks - load->niceTicks ) ) / totalTicks;
    load->idleLoad = ( 100 - ( load->userLoad + load->sysLoad + load->niceLoad ) );
  } else
    load->userLoad = load->sysLoad = load->niceLoad = load->idleLoad = 0;

  load->userTicks = currUserTicks;
  load->sysTicks = currSysTicks;
  load->niceTicks = currNiceTicks;
  load->idleTicks = currIdleTicks;
}

static int processDisk( char* tag, char* buf, const char* label, int idx )
{
  if ( strcmp( label, tag ) == 0 ) {
    unsigned long val;
    unsigned int i;
    buf = buf + strlen( label ) + 1;

    for ( i = 0; i < DiskCount; ++i ) {
      sscanf( buf, "%lu", &val );
      while ( *buf && isblank( *buf++ ) );
      while ( *buf && isdigit( *buf++ ) );
      DiskLoad[ i ].s[ idx ].delta = val - DiskLoad[ i ].s[ idx ].old;
      DiskLoad[ i ].s[ idx ].old = val;
    }

    return 1;
  }

  return 0;
}

static int processDiskIO( const char* buf )
{
  /* Process disk_io lines as provided by 2.4.x kernels.
   * disk_io: (2,0):(3,3,6,0,0) (3,0):(1413012,511622,12155382,901390,26486215) */
  int major, minor;
  unsigned long total, rblk, rio, wblk, wio;
  DiskIOInfo* ptr = DiskIO;
  DiskIOInfo* last = 0;
  char sensorName[ 128 ];
  const char* p;

  p = buf + strlen( "disk_io: " );
  while ( p && *p ) {
    if ( sscanf( p, "(%d,%d):(%lu,%lu,%lu,%lu,%lu)", &major, &minor,
                 &total, &rio, &rblk, &wio, &wblk ) != 7 )
      return -1;

    last = 0;
    ptr = DiskIO;
    while ( ptr ) {
      if ( ptr->major == major && ptr->minor == minor ) {
        /* The IO device has already been registered. */
        ptr->total.delta = total - ptr->total.old;
        ptr->total.old = total;
        ptr->rio.delta = rio - ptr->rio.old;
        ptr->rio.old = rio;
        ptr->wio.delta = wio - ptr->wio.old;
        ptr->wio.old = wio;
        ptr->rblk.delta = rblk - ptr->rblk.old;
        ptr->rblk.old = rblk;
        ptr->wblk.delta = wblk - ptr->wblk.old;
        ptr->wblk.old = wblk;
        ptr->alive = 1;
        break;
      }
      last = ptr;
      ptr = ptr->next;
    }

    if ( !ptr ) {
      /* The IO device has not been registered yet. We need to add it. */
      ptr = (DiskIOInfo*)malloc( sizeof( DiskIOInfo ) );
      ptr->major = major;
      ptr->minor = minor;
      ptr->total.delta = 0;
      ptr->total.old = total;
      ptr->rio.delta = 0;
      ptr->rio.old = rio;
      ptr->wio.delta = 0;
      ptr->wio.old = wio;
      ptr->rblk.delta = 0;
      ptr->rblk.old = rblk;
      ptr->wblk.delta = 0;
      ptr->wblk.old = wblk;
      ptr->alive = 1;
      ptr->next = 0;
      if ( last ) {
        /* Append new entry at end of list. */
        last->next = ptr;
      } else {
        /* List is empty, so we insert the fist element into the list. */
        DiskIO = ptr;
      }

      sprintf( sensorName, "disk/%d:%d/total", major, minor );
      registerMonitor( sensorName, "integer", printDiskIO, printDiskIOInfo, StatSM );
      sprintf( sensorName, "disk/%d:%d/rio", major, minor );
      registerMonitor( sensorName, "integer", printDiskIO, printDiskIOInfo, StatSM );
      sprintf( sensorName, "disk/%d:%d/wio", major, minor );
      registerMonitor( sensorName, "integer", printDiskIO, printDiskIOInfo, StatSM );
      sprintf( sensorName, "disk/%d:%d/rblk", major, minor );
      registerMonitor( sensorName, "integer", printDiskIO, printDiskIOInfo, StatSM );
      sprintf( sensorName, "disk/%d:%d/wblk", major, minor );
      registerMonitor( sensorName, "integer", printDiskIO, printDiskIOInfo, StatSM );
    }
    /* Move p after the sencond ')'. We can safely assume that
     * those two ')' exist. */
    p = strchr( p, ')' ) + 1;
    p = strchr( p, ')' ) + 1;
    if ( p && *p )
      p = strchr( p, '(' );
  }

  return 0;
}

static int process26DiskIO( const char* buf )
{
   /* Process values from /proc/diskstats (Linux >= 2.6.x) */

   /* For each disk /proc/diskstats includes lines as follows:
    *   3    0 hda 1314558 74053 26451438 14776742 1971172 4607401 52658448 202855090 0 9597019 217637839
    *   3    1 hda1 178 360 0 0
    *   3    2 hda2 354 360 0 0
    *   3    3 hda3 354 360 0 0
    *   3    4 hda4 0 0 0 0
    *   3    5 hda5 529506 9616000 4745856 37966848
    *
    * - See Documentation/iostats.txt for details on the changes
    */
   int                      major, minor;
   char                     devname[16];
   unsigned long            total,
                            rio, rmrg, rblk, rtim,
                            wio, wmrg, wblk, wtim,
                            ioprog, iotim, iotimw;
   DiskIOInfo               *ptr = DiskIO;
   DiskIOInfo               *last = 0;
   char                     sensorName[128];

   switch (sscanf(buf, "%d %d %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
              &major, &minor, devname,
              &rio, &rmrg, &rblk, &rtim,
              &wio, &wmrg, &wblk, &wtim,
              &ioprog, &iotim, &iotimw))
   {
      case 7:
         /* Partition stats entry */
         /* Adjust read fields rio rmrg rblk rtim -> rio rblk wio wblk */
         wblk = rtim;
         wio = rblk;
         rblk = rmrg;

         total = rio + wio;

         break;
      case 14:
         /* Disk stats entry */
         total = rio + wio;

         break;
      default:
         /* Something unexepected */
         return -1;
   }

   last = 0;
   ptr = DiskIO;
   while (ptr)
   {
      if (ptr->major == major && ptr->minor == minor)
      {
         /* The IO device has already been registered. */
         ptr->total.delta = total - ptr->total.old;
         ptr->total.old = total;
         ptr->rio.delta = rio - ptr->rio.old;
         ptr->rio.old = rio;
         ptr->wio.delta = wio - ptr->wio.old;
         ptr->wio.old = wio;
         ptr->rblk.delta = rblk - ptr->rblk.old;
         ptr->rblk.old = rblk;
         ptr->wblk.delta = wblk - ptr->wblk.old;
         ptr->wblk.old = wblk;
         ptr->alive = 1;
         break;
      }

      last = ptr;
      ptr = ptr->next;
   }

   if (!ptr)
   {
      /* The IO device has not been registered yet. We need to add it. */
      ptr = (DiskIOInfo*)malloc( sizeof( DiskIOInfo ) );
      ptr->major = major;
      ptr->minor = minor;
      ptr->total.delta = 0;
      ptr->total.old = total;
      ptr->rio.delta = 0;
      ptr->rio.old = rio;
      ptr->wio.delta = 0;
      ptr->wio.old = wio;
      ptr->rblk.delta = 0;
      ptr->rblk.old = rblk;
      ptr->wblk.delta = 0;
      ptr->wblk.old = wblk;
      ptr->alive = 1;
      ptr->next = 0;
      if (last)
      {
         /* Append new entry at end of list. */
         last->next = ptr;
      }
      else
      {
         /* List is empty, so we insert the fist element into the list. */
         DiskIO = ptr;
      }

      sprintf(sensorName, "disk/%d:%d/total", major, minor);
      registerMonitor(sensorName, "integer", printDiskIO, printDiskIOInfo,
         StatSM);
      sprintf(sensorName, "disk/%d:%d/rio", major, minor);
      registerMonitor(sensorName, "integer", printDiskIO, printDiskIOInfo,
         StatSM);
      sprintf(sensorName, "disk/%d:%d/wio", major, minor);
      registerMonitor(sensorName, "integer", printDiskIO, printDiskIOInfo,
         StatSM);
      sprintf(sensorName, "disk/%d:%d/rblk", major, minor);
      registerMonitor(sensorName, "integer", printDiskIO, printDiskIOInfo,
         StatSM);
      sprintf(sensorName, "disk/%d:%d/wblk", major, minor);
      registerMonitor(sensorName, "integer", printDiskIO, printDiskIOInfo,
         StatSM);
   }

   return 0;
}

static void cleanupDiskList( void )
{
  DiskIOInfo* ptr = DiskIO;
  DiskIOInfo* last = 0;

  while ( ptr ) {
    if ( ptr->alive == 0 ) {
      DiskIOInfo* newPtr;
      char sensorName[ 128 ];

      /* Disk device has disappeared. We have to remove it from
       * the list and unregister the monitors. */
      sprintf( sensorName, "disk/%d:%d/total", ptr->major, ptr->minor );
      removeMonitor( sensorName );
      sprintf( sensorName, "disk/%d:%d/rio", ptr->major, ptr->minor );
      removeMonitor( sensorName );
      sprintf( sensorName, "disk/%d:%d/wio", ptr->major, ptr->minor );
      removeMonitor( sensorName );
      sprintf( sensorName, "disk/%d:%d/rblk", ptr->major, ptr->minor );
      removeMonitor( sensorName );
      sprintf( sensorName, "disk/%d:%d/wblk", ptr->major, ptr->minor );
      removeMonitor( sensorName );
      if ( last ) {
        last->next = ptr->next;
        newPtr = ptr->next;
      } else {
        DiskIO = ptr->next;
        newPtr = DiskIO;
        last = 0;
      }

      free ( ptr );
      ptr = newPtr;
    } else {
      ptr->alive = 0;
      last = ptr;
      ptr = ptr->next;
    }
  }
}

static void processStat( void )
{
  char format[ 32 ];
  char tagFormat[ 16 ];
  char buf[ 1024 ];
  char tag[ 32 ];
  char* statBufP = StatBuf;
  char* vmstatBufP = VmStatBuf;
  char* iostatBufP = IOStatBuf;

  sprintf( format, "%%%d[^\n]\n", (int)sizeof( buf ) - 1 );
  sprintf( tagFormat, "%%%ds", (int)sizeof( tag ) - 1 );

  while ( sscanf( statBufP, format, buf ) == 1 ) {
    buf[ sizeof( buf ) - 1 ] = '\0';
    statBufP += strlen( buf ) + 1;  /* move statBufP to next line */
    sscanf( buf, tagFormat, tag );

    if ( strcmp( "cpu", tag ) == 0 ) {
      /* Total CPU load */
      updateCPULoad( buf, &CPULoad );
    } else if ( strncmp( "cpu", tag, 3 ) == 0 ) {
      /* Load for each SMP CPU */
      int id;
      sscanf( tag + 3, "%d", &id );
      updateCPULoad( buf, &SMPLoad[ id ]  );
    } else if ( processDisk( tag, buf, "disk", 0 ) ) {
    } else if ( processDisk( tag, buf, "disk_rio", 1 ) ) {
    } else if ( processDisk( tag, buf, "disk_wio", 2 ) ) {
    } else if ( processDisk( tag, buf, "disk_rblk", 3 ) ) {
    } else if ( processDisk( tag, buf, "disk_wblk", 4 ) ) {
    } else if ( strcmp( "disk_io:", tag ) == 0 ) {
      processDiskIO( buf );
    } else if ( strcmp( "page", tag ) == 0 ) {
      unsigned long v1, v2;
      sscanf( buf + 5, "%lu %lu", &v1, &v2 );
      PageIn = v1 - OldPageIn;
      OldPageIn = v1;
      PageOut = v2 - OldPageOut;
      OldPageOut = v2;
    } else if ( strcmp( "intr", tag ) == 0 ) {
      unsigned int i = 0;
      char* p = buf + 5;

      for ( i = 0; i < NumOfInts; i++ ) {
        unsigned long val;

        sscanf( p, "%lu", &val );
        Intr[ i ] = val - OldIntr[ i ];
        OldIntr[ i ] = val;
        while ( *p && *p != ' ' )
          p++;
        while ( *p && *p == ' ' )
          p++;
      }
    } else if ( strcmp( "ctxt", tag ) == 0 ) {
      unsigned long val;

      sscanf( buf + 5, "%lu", &val );
      Ctxt = val - OldCtxt;
      OldCtxt = val;
    }
  }

  /* Read Linux 2.5.x /proc/vmstat */
  while ( sscanf( vmstatBufP, format, buf ) == 1 ) {
    buf[ sizeof( buf ) - 1 ] = '\0';
    vmstatBufP += strlen( buf ) + 1;  /* move vmstatBufP to next line */
    sscanf( buf, tagFormat, tag );

    if ( strcmp( "pgpgin", tag ) == 0 ) {
      unsigned long v1;
      sscanf( buf + 7, "%lu", &v1 );
      PageIn = v1 - OldPageIn;
      OldPageIn = v1;
    } else if ( strcmp( "pgpgout", tag ) == 0 ) {
      unsigned long v1;
      sscanf( buf + 7, "%lu", &v1 );
      PageOut = v1 - OldPageOut;
      OldPageOut = v1;
    }
  }

  /* Process values from /proc/diskstats (Linux >= 2.6.x) */
  while (sscanf(iostatBufP, format, buf) == 1)
  {
    buf[sizeof(buf) - 1] = '\0';
    iostatBufP += strlen(buf) + 1;  /* move IOstatBufP to next line */

    process26DiskIO(buf);
  }

  /* save exact time inverval between this and the last read of /proc/stat */
  timeInterval = currSampling.tv_sec - lastSampling.tv_sec +
                 ( currSampling.tv_usec - lastSampling.tv_usec ) / 1000000.0;
  lastSampling = currSampling;

  cleanupDiskList();

  Dirty = 0;
}

/*
================================ public part =================================
*/

void initStat( struct SensorModul* sm )
{
  /* The CPU load is calculated from the values in /proc/stat. The cpu
   * entry contains 4 counters. These counters count the number of ticks
   * the system has spend on user processes, system processes, nice
   * processes and idle time.
   *
   * SMP systems will have cpu1 to cpuN lines right after the cpu info. The
   * format is identical to cpu and reports the information for each cpu.
   * Linux kernels <= 2.0 do not provide this information!
   *
   * The /proc/stat file looks like this:
   *
   * cpu  1586 4 808 36274
   * disk 7797 0 0 0
   * disk_rio 6889 0 0 0
   * disk_wio 908 0 0 0
   * disk_rblk 13775 0 0 0
   * disk_wblk 1816 0 0 0
   * page 27575 1330
   * swap 1 0
   * intr 50444 38672 2557 0 0 0 0 2 0 2 0 0 3 1429 1 7778 0
   * ctxt 54155
   * btime 917379184
   * processes 347 
   *
   * Linux kernel >= 2.4.0 have one or more disk_io: lines instead of
   * the disk_* lines.
   *
   * Linux kernel >= 2.6.x(?) have disk I/O stats in /proc/diskstats
   * and no disk relevant lines are found in /proc/stat
   */

  char format[ 32 ];
  char tagFormat[ 16 ];
  char buf[ 1024 ];
  char tag[ 32 ];
  char* statBufP = StatBuf;
  char* vmstatBufP = VmStatBuf;
  char* iostatBufP = IOStatBuf;

  StatSM = sm;

  updateStat();

  sprintf( format, "%%%d[^\n]\n", (int)sizeof( buf ) - 1 );
  sprintf( tagFormat, "%%%ds", (int)sizeof( tag ) - 1 );

  while ( sscanf( statBufP, format, buf ) == 1 ) {
    buf[ sizeof( buf ) - 1 ] = '\0';
    statBufP += strlen( buf ) + 1;  /* move statBufP to next line */
    sscanf( buf, tagFormat, tag );

    if ( strcmp( "cpu", tag ) == 0 ) {
      /* Total CPU load */
      registerMonitor( "cpu/user", "integer", printCPUUser, printCPUUserInfo, StatSM );
      registerMonitor( "cpu/nice", "integer", printCPUNice, printCPUNiceInfo, StatSM );
      registerMonitor( "cpu/sys", "integer", printCPUSys, printCPUSysInfo, StatSM );
      registerMonitor( "cpu/idle", "integer", printCPUIdle, printCPUIdleInfo, StatSM );
    } else if ( strncmp( "cpu", tag, 3 ) == 0 ) {
      char cmdName[ 24 ];
      /* Load for each SMP CPU */
      int id;

      sscanf( tag + 3, "%d", &id );
      CPUCount++;
      sprintf( cmdName, "cpu%d/user", id );
      registerMonitor( cmdName, "integer", printCPUxUser, printCPUxUserInfo, StatSM );
      sprintf( cmdName, "cpu%d/nice", id );
      registerMonitor( cmdName, "integer", printCPUxNice, printCPUxNiceInfo, StatSM );
      sprintf( cmdName, "cpu%d/sys", id );
      registerMonitor( cmdName, "integer", printCPUxSys, printCPUxSysInfo, StatSM );
      sprintf( cmdName, "cpu%d/idle", id );
      registerMonitor( cmdName, "integer", printCPUxIdle, printCPUxIdleInfo, StatSM );
    } else if ( strcmp( "disk", tag ) == 0 ) {
      unsigned long val;
      char* b = buf + 5;

      /* Count the number of registered disks */
      for ( DiskCount = 0; *b && sscanf( b, "%lu", &val ) == 1; DiskCount++ ) {
        while ( *b && isblank( *b++ ) );
        while ( *b && isdigit( *b++ ) );
      }

      if ( DiskCount > 0 )
        DiskLoad = (DiskLoadInfo*)malloc( sizeof( DiskLoadInfo ) * DiskCount );
        initStatDisk( tag, buf, "disk", "disk", 0, printDiskTotal, printDiskTotalInfo );
    } else if ( initStatDisk( tag, buf, "disk_rio", "rio", 1, printDiskRIO,
                              printDiskRIOInfo ) );
    else if ( initStatDisk( tag, buf, "disk_wio", "wio", 2, printDiskWIO,
                            printDiskWIOInfo ) );
    else if ( initStatDisk( tag, buf, "disk_rblk", "rblk", 3, printDiskRBlk,
                            printDiskRBlkInfo ) );
    else if ( initStatDisk( tag, buf, "disk_wblk", "wblk", 4, printDiskWBlk,
                            printDiskWBlkInfo ) );
    else if ( strcmp( "disk_io:", tag ) == 0 )
      processDiskIO( buf );
    else if ( strcmp( "page", tag ) == 0 ) {
      sscanf( buf + 5, "%lu %lu", &OldPageIn, &OldPageOut );
      registerMonitor( "cpu/pageIn", "integer", printPageIn,
                       printPageInInfo, StatSM );
      registerMonitor( "cpu/pageOut", "integer", printPageOut,
                       printPageOutInfo, StatSM );
    } else if ( strcmp( "intr", tag ) == 0 ) {
      unsigned int i;
      char cmdName[ 32 ];
      char* p = buf + 5;

      /* Count the number of listed values in the intr line. */
      NumOfInts = 0;
      while ( *p )
        if ( *p++ == ' ' )
          NumOfInts++;

      /* It looks like anything above 24 is always 0. So let's just
       * ignore this for the time being. */
      if ( NumOfInts > 25 )
        NumOfInts = 25;
      OldIntr = (unsigned long*)malloc( NumOfInts * sizeof( unsigned long ) );
      Intr = (unsigned long*)malloc( NumOfInts * sizeof( unsigned long ) );
      i = 0;
      p = buf + 5;
      for ( i = 0; p && i < NumOfInts; i++ ) {
        sscanf( p, "%lu", &OldIntr[ i ] );
        while ( *p && *p != ' ' )
          p++;
        while ( *p && *p == ' ' )
          p++;
        sprintf( cmdName, "cpu/interrupts/int%02d", i );
        registerMonitor( cmdName, "integer", printInterruptx,
                         printInterruptxInfo, StatSM );
      }
    } else if ( strcmp( "ctxt", tag ) == 0 ) {
      sscanf( buf + 5, "%lu", &OldCtxt );
      registerMonitor( "cpu/context", "integer", printCtxt,
                       printCtxtInfo, StatSM );
    }
  }

  while ( sscanf( vmstatBufP, format, buf ) == 1 ) {
    buf[ sizeof( buf ) - 1 ] = '\0';
    vmstatBufP += strlen( buf ) + 1;  /* move vmstatBufP to next line */
    sscanf( buf, tagFormat, tag );

    if ( strcmp( "pgpgin", tag ) == 0 ) {
      sscanf( buf + 7, "%lu", &OldPageIn );
      registerMonitor( "cpu/pageIn", "integer", printPageIn,
                       printPageInInfo, StatSM );
    } else if ( strcmp( "pgpgout", tag ) == 0 ) {
      sscanf( buf + 7, "%lu", &OldPageOut );
      registerMonitor( "cpu/pageOut", "integer", printPageOut,
                       printPageOutInfo, StatSM );
    }
  }

   /* Process values from /proc/diskstats (Linux >= 2.6.x) */
   while (sscanf(iostatBufP, format, buf) == 1)
   {
      buf[sizeof(buf) - 1] = '\0';
      iostatBufP += strlen(buf) + 1;  /* move IOstatBufP to next line */

      process26DiskIO(buf);
   }

  if ( CPUCount > 0 )
    SMPLoad = (CPULoadInfo*)malloc( sizeof( CPULoadInfo ) * CPUCount );

  /* Call processStat to eliminate initial peek values. */
  processStat();
}

void exitStat( void )
{
  free( DiskLoad );
  DiskLoad = 0;

  free( SMPLoad );
  SMPLoad = 0;

  free( OldIntr );
  OldIntr = 0;

  free( Intr );
  Intr = 0;
}

int updateStat( void )
{
  size_t n;
  int fd;

  if ( ( fd = open( "/proc/stat", O_RDONLY ) ) < 0 ) {
    print_error( "Cannot open file \'/proc/stat\'!\n"
                 "The kernel needs to be compiled with support\n"
                 "for /proc filesystem enabled!\n" );
    return -1;
  }

  if ( ( n = read( fd, StatBuf, STATBUFSIZE - 1 ) ) == STATBUFSIZE - 1 ) {
    log_error( "Internal buffer too small to read \'/proc/stat\'" );

    close( fd );
    return -1;
  }

  gettimeofday( &currSampling, 0 );
  close( fd );
  StatBuf[ n ] = '\0';
  Dirty = 1;

  VmStatBuf[ 0 ] = '\0';
  if ( ( fd = open( "/proc/vmstat", O_RDONLY ) ) < 0 )
    return 0; /* failure is okay, only exists for Linux >= 2.5.x */

  if ( ( n = read( fd, VmStatBuf, STATBUFSIZE - 1 ) ) == STATBUFSIZE - 1 ) {
    log_error( "Internal buffer too small to read \'/proc/vmstat\'" );

    close( fd );
    return -1;
  }

  close( fd );
  VmStatBuf[ n ] = '\0';

  /* Linux >= 2.6.x has disk I/O stats in /proc/diskstats */
  IOStatBuf[ 0 ] = '\0';
  if ( ( fd = open( "/proc/diskstats", O_RDONLY ) ) < 0 )
    return 0; /* failure is okay, only exists for Linux >= 2.6.x */

  if ( ( n = read( fd, IOStatBuf, STATBUFSIZE - 1 ) ) == STATBUFSIZE - 1 ) {
    log_error( "Internal buffer too small to read \'/proc/diskstats\'" );

    close( fd );
    return -1;
  }

  close( fd );
  IOStatBuf[ n ] = '\0';

  return 0;
}

void printCPUUser( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%d\n", CPULoad.userLoad );
}

void printCPUUserInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "CPU User Load\t0\t100\t%%\n" );
}

void printCPUNice( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%d\n", CPULoad.niceLoad );
}

void printCPUNiceInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "CPU Nice Load\t0\t100\t%%\n" );
}

void printCPUSys( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%d\n", CPULoad.sysLoad );
}

void printCPUSysInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "CPU System Load\t0\t100\t%%\n" );
}

void printCPUIdle( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%d\n", CPULoad.idleLoad );
}

void printCPUIdleInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "CPU Idle Load\t0\t100\t%%\n" );
}

void printCPUxUser( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "%d\n", SMPLoad[ id ].userLoad );
}

void printCPUxUserInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "CPU%d User Load\t0\t100\t%%\n", id );
}

void printCPUxNice( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "%d\n", SMPLoad[ id ].niceLoad );
}

void printCPUxNiceInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "CPU%d Nice Load\t0\t100\t%%\n", id );
}

void printCPUxSys( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "%d\n", SMPLoad[ id ].sysLoad );
}

void printCPUxSysInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "CPU%d System Load\t0\t100\t%%\n", id );
}

void printCPUxIdle( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "%d\n", SMPLoad[ id ].idleLoad );
}

void printCPUxIdleInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 3, "%d", &id );
  fprintf( CurrentClient, "CPU%d Idle Load\t0\t100\t%%\n", id );
}

void printDiskTotal( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "%lu\n", (unsigned long)( DiskLoad[ id ].s[ 0 ].delta
                                                    / timeInterval ) );
}

void printDiskTotalInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "Disk%d Total Load\t0\t0\tkBytes/s\n", id );
}

void printDiskRIO( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "%lu\n", (unsigned long)( DiskLoad[ id ].s[ 1 ].delta
                                                    / timeInterval ) );
}

void printDiskRIOInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "Disk%d Read\t0\t0\tkBytes/s\n", id );
}

void printDiskWIO( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "%lu\n", (unsigned long)( DiskLoad[ id ].s[ 2 ].delta
                                                    / timeInterval ) );
}

void printDiskWIOInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "Disk%d Write\t0\t0\tkBytes/s\n", id );
}

void printDiskRBlk( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 9, "%d", &id );
  /* a block is 512 bytes or 1/2 kBytes */
  fprintf( CurrentClient, "%lu\n", (unsigned long)( DiskLoad[ id ].s[ 3 ].delta
                                                    / timeInterval * 2 ) );
}

void printDiskRBlkInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "Disk%d Read Data\t0\t0\tkBytes/s\n", id );
}

void printDiskWBlk( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + 9, "%d", &id );
  /* a block is 512 bytes or 1/2 kBytes */
  fprintf( CurrentClient, "%lu\n", (unsigned long)( DiskLoad[ id ].s[ 4 ].delta
                                                    / timeInterval * 2 ) );
}

void printDiskWBlkInfo( const char* cmd )
{
  int id;

  sscanf( cmd + 9, "%d", &id );
  fprintf( CurrentClient, "Disk%d Write Data\t0\t0\tkBytes/s\n", id );
}

void printPageIn( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%lu\n", (unsigned long)( PageIn / timeInterval ) );
}

void printPageInInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "Paged in Pages\t0\t0\t1/s\n" );
}

void printPageOut( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%lu\n", (unsigned long)( PageOut / timeInterval ) );
}

void printPageOutInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "Paged out Pages\t0\t0\t1/s\n" );
}

void printInterruptx( const char* cmd )
{
  int id;

  if ( Dirty )
    processStat();

  sscanf( cmd + strlen( "cpu/interrupts/int" ), "%d", &id );
  fprintf( CurrentClient, "%lu\n", (unsigned long)( Intr[ id ] / timeInterval ) );
}

void printInterruptxInfo( const char* cmd )
{
  int id;

  sscanf( cmd + strlen( "cpu/interrupt/int" ), "%d", &id );
  fprintf( CurrentClient, "Interrupt %d\t0\t0\t1/s\n", id );
}

void printCtxt( const char* cmd )
{
  (void)cmd;

  if ( Dirty )
    processStat();

  fprintf( CurrentClient, "%lu\n", (unsigned long)( Ctxt / timeInterval ) );
}

void printCtxtInfo( const char* cmd )
{
  (void)cmd;
  fprintf( CurrentClient, "Context switches\t0\t0\t1/s\n" );
}

void printDiskIO( const char* cmd )
{
  int major, minor;
  char name[ 17 ];
  DiskIOInfo* ptr;

  sscanf( cmd, "disk/%d:%d/%16s", &major, &minor, name );

  if ( Dirty )
    processStat();

  ptr = DiskIO;
  while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
    ptr = ptr->next;

  if ( !ptr ) {
    print_error( "RECONFIGURE" );
    fprintf( CurrentClient, "0\n" );

    log_error( "Disk device disappeared" );
    return;
  }

  if ( strcmp( name, "total" ) == 0 )
    fprintf( CurrentClient, "%lu\n", (unsigned long)( ptr->total.delta
                                                      / timeInterval ) );
  else if ( strcmp( name, "rio" ) == 0 )
    fprintf( CurrentClient, "%lu\n", (unsigned long)( ptr->rio.delta
                                                      / timeInterval ) );
  else if ( strcmp( name, "wio" ) == 0 )
    fprintf( CurrentClient, "%lu\n", (unsigned long)( ptr->wio.delta
                                                      / timeInterval ) );
  else if ( strcmp( name, "rblk" ) == 0 )
    fprintf( CurrentClient, "%lu\n", (unsigned long)( ptr->rblk.delta
                                                      / ( timeInterval * 2 ) ) );
  else if ( strcmp( name, "wblk" ) == 0 )
    fprintf( CurrentClient, "%lu\n", (unsigned long)( ptr->wblk.delta
                                                      / ( timeInterval * 2 ) ) );
  else {
    fprintf( CurrentClient, "0\n" );
    log_error( "Unknown disk device property \'%s\'", name );
  }
}

void printDiskIOInfo( const char* cmd )
{
  int major, minor;
  char name[ 17 ];
  DiskIOInfo* ptr = DiskIO;

  sscanf( cmd, "disk/%d:%d/%16s", &major, &minor, name );

  while ( ptr && ( ptr->major != major || ptr->minor != minor ) )
    ptr = ptr->next;

  if ( !ptr ) {
    /* Disk device has disappeared. Print a dummy answer. */
    fprintf( CurrentClient, "Dummy\t0\t0\t\n" );
    return;
  }

  /* remove trailing '?' */
  name[ strlen( name ) - 1 ] = '\0';

  if ( strcmp( name, "total" ) == 0 )
    fprintf( CurrentClient, "Total accesses device %d, %d\t0\t0\t1/s\n",
             major, minor );
  else if ( strcmp( name, "rio" ) == 0 )
    fprintf( CurrentClient, "Read data device %d, %d\t0\t0\t1/s\n",
             major, minor );
  else if ( strcmp( name, "wio" ) == 0 )
    fprintf( CurrentClient, "Write data device %d, %d\t0\t0\t1/s\n",
             major, minor );
  else if ( strcmp( name, "rblk" ) == 0 )
    fprintf( CurrentClient, "Read accesses device %d, %d\t0\t0\tkBytes/s\n",
             major, minor );
  else if ( strcmp( name, "wblk" ) == 0 )
    fprintf( CurrentClient, "Write accesses device %d, %d\t0\t0\tkBytes/s\n",
             major, minor );
  else {
    fprintf( CurrentClient, "Dummy\t0\t0\t\n" );
    log_error( "Request for unknown device property \'%s\'",	name );
  }
}
