//
//  Copyright (c) 1999, 2006 by Mike Romberg (mike.romberg@noaa.gov)
//
//  This file may be distributed under terms of the GPL
//
// $Id$
//

#include "diskmeter.h"
#include "xosview.h"
#include <fstream>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>


#define MAX_PROCSTAT_LENGTH 2048

DiskMeter::DiskMeter( XOSView *parent, float max ) : FieldMeterGraph(
  parent, 3, "DISK", "READ/WRITE/IDLE"), _vmstat(false),
  _statFileName("/proc/stat")
{
    read_prev_ = 0;
    write_prev_ = 0;
    maxspeed_ = max;

    _sysfs=_vmstat=false;
    sysfs_read_prev_=sysfs_write_prev_=0L;
    struct stat buf;

    // first - try sysfs:
    if (stat("/sys/block", &buf) == 0
      && buf.st_mode & S_IFDIR) {

        _sysfs = true;
        _statFileName = "/sys/block";
        XOSDEBUG("diskmeter: using sysfs /sys/block\n");
        getsysfsdiskinfo();

    } else  // try vmstat:
    if (stat("/proc/vmstat", &buf) == 0
      && buf.st_mode & S_IFREG) {

        _vmstat = true;
        _sysfs  = false;
        _statFileName = "/proc/vmstat";
        getvmdiskinfo();

    } else // fall back to stat
        getdiskinfo();


}

DiskMeter::~DiskMeter( void )
    {
    }

void DiskMeter::checkResources( void )
    {
    FieldMeterGraph::checkResources();

    setfieldcolor( 0, parent_->getResource("diskReadColor") );
    setfieldcolor( 1, parent_->getResource("diskWriteColor") );
    setfieldcolor( 2, parent_->getResource("diskIdleColor") );
    priority_ = atoi (parent_->getResource( "diskPriority" ) );
    dodecay_ = parent_->isResourceTrue("diskDecay" );
    useGraph_ = parent_->isResourceTrue( "diskGraph" );
    SetUsedFormat(parent_->getResource("diskUsedFormat"));
    }

void DiskMeter::checkevent( void )
    {
    if (_vmstat)
        getvmdiskinfo();
    else if ( _sysfs )
        getsysfsdiskinfo();
    else
        getdiskinfo();

    drawfields();

    }

// IMHO the logic here is quite broken - but for backward compat UNCHANGED:
void DiskMeter::updateinfo(unsigned long one, unsigned long two,
  int fudgeFactor)
    {
    // assume each "unit" is 1k.
    // This is true for ext2, but seems to be 512 bytes
    // for vfat and 2k for cdroms
    // work in 512-byte blocks

    // tw: strange, on my system, a ext2fs (read and write)
    // unit seems to be 2048. kernel 2.2.12 and the file system
    // is on a SW-RAID5 device (/dev/md0).

    // So this is a FIXME - but how ???

    float itim = IntervalTimeInMicrosecs();
    unsigned long read_curr = one * fudgeFactor;  // FIXME!
    unsigned long write_curr = two * fudgeFactor; // FIXME!

    // avoid strange values at first call
    if(read_prev_ == 0) read_prev_ = read_curr;
    if(write_prev_ == 0) write_prev_ = write_curr;

    // calculate rate in bytes per second
    fields_[0] = ((read_curr - read_prev_) * 1e6 * 512) / itim;
    fields_[1] = ((write_curr - write_prev_) * 1e6 * 512) / itim;

    // fix overflow (conversion bug?)
    if (fields_[0] < 0.0)
        fields_[0] = 0.0;
    if (fields_[1] < 0.0)
        fields_[1] = 0.0;

    if (fields_[0] + fields_[1] > total_)
       	total_ = fields_[0] + fields_[1];

    fields_[2] = total_ - (fields_[0] + fields_[1]);

    read_prev_ = read_curr;
    write_prev_ = write_curr;

    setUsed((fields_[0]+fields_[1]), total_);
    IntervalTimerStart();
    }

void DiskMeter::getvmdiskinfo(void)
{
    IntervalTimerStop();
    total_ = maxspeed_;
    char buf[MAX_PROCSTAT_LENGTH];
    std::ifstream stats(_statFileName);
    unsigned long one, two;

    if ( !stats )
        {
        std::cerr <<"Can not open file : " << _statFileName << std::endl;
        exit( 1 );
        }


    stats >> buf;
    // kernel >= 2.5
    while (!stats.eof() && strncmp(buf, "pgpgin", 7))
        {
        stats.ignore(1024, '\n');
        stats >> buf;
        }

    // read first value
    stats >> one;

    while (!stats.eof() && strncmp(buf, "pgpgout", 7))
        {
        stats.ignore(1024, '\n');
        stats >> buf;
        }

    // read second value
    stats >> two;

    updateinfo(one, two, 4);
}

void DiskMeter::getdiskinfo( void )
{
    IntervalTimerStop();
    total_ = maxspeed_;
    char buf[MAX_PROCSTAT_LENGTH];
    std::ifstream stats(_statFileName);

    if ( !stats )
    {
        std::cerr <<"Can not open file : " << _statFileName << std::endl;
        exit( 1 );
    }

    // Find the line with 'page'
    stats >> buf;
    while (strncmp(buf, "disk_io:", 8))
    {
        stats.ignore(MAX_PROCSTAT_LENGTH, '\n');
        stats >> buf;
        if (stats.eof())
            break;
    }

    // read values
    unsigned long one=0, two=0;
    unsigned long junk,read1,write1;
    stats >> buf;
    while (7 == sscanf(buf,"(%lu,%lu):(%lu,%lu,%lu,%lu,%lu)",&junk,&junk,&junk,&junk,&read1,&junk,&write1))
    {
        one += read1;
        two += write1;
        stats >> buf;
    }

    updateinfo(one, two, 1);
}

// sysfs version - works with long-long !!
// (no dependency on sector-size here )
void DiskMeter::update_info(unsigned long long rsum, unsigned long long wsum)
{
    float itim = IntervalTimeInMicrosecs();

    // avoid strange values at first call
    // (by this - the first value displayed becomes zero)
    if(sysfs_read_prev_ == 0L)  sysfs_read_prev_  = rsum;
    if(sysfs_write_prev_ == 0L) sysfs_write_prev_ = wsum;

    // convert rate from bytes/microsec into bytes/second
    fields_[0] = ((rsum - sysfs_read_prev_ ) * 1e6 ) / itim;
    fields_[1] = ((wsum - sysfs_write_prev_) * 1e6 ) / itim;

    // fix overflow (conversion bug?)
    if (fields_[0] < 0.0)
        fields_[0] = 0.0;
    if (fields_[1] < 0.0)
        fields_[1] = 0.0;

    // bump up max total:
    if (fields_[0] + fields_[1] > total_)
        total_ = fields_[0] + fields_[1];

    fields_[2] = total_ - (fields_[0] + fields_[1]);

    // save old vals for next round
    sysfs_read_prev_  = rsum;
    sysfs_write_prev_ = wsum;

    setUsed((fields_[0]+fields_[1]), total_);
    IntervalTimerStart();
}



// XXX: sysfs - read Documentation/iostats.txt !!!
// extract stats from /sys/block/*/stat
// each disk reports a 32bit u_int, which can WRAP around
// XXX: currently sector-size is fixed 512bytes
//      (would need a sysfs-val for sect-size)

void DiskMeter::getsysfsdiskinfo( void )
{
        // field-3: sects read since boot (but can wrap!)
        // field-7: sects written since boot (but can wrap!)
        // just sum up everything in /sys/block/*/stat

  std::string sysfs_dir = _statFileName;
  std::string disk;
  struct stat buf;
  std::ifstream diskstat;

  // the sum of all disks:
  unsigned long long all_bytes_read,all_bytes_written;

  // ... while this is only one disk's value:
  unsigned long sec_read,sec_written;
  unsigned long sect_size;

  unsigned long dummy;

  IntervalTimerStop();
  total_ = maxspeed_;

  DIR *dir = opendir(_statFileName);
  if (dir==NULL) {
    XOSDEBUG("sysfs: Cannot open directory : %s\n", _statFileName);
    return;
  }

  // reset all sums
  all_bytes_read=all_bytes_written=0L;
  sect_size=0L;

  // visit every /sys/block/*/stat and sum up the values:

  for (struct dirent *dirent; (dirent = readdir(dir)) != NULL; ) {
    if (strncmp(dirent->d_name, ".", 1) == 0
        || strncmp(dirent->d_name, "..", 2) == 0)
      continue;

    disk = sysfs_dir + "/" + dirent->d_name;

    if (stat(disk.c_str(), &buf) == 0 && buf.st_mode & S_IFDIR) {
       // is a dir, locate 'stat' file in it
       disk = disk + "/stat";
       if (stat(disk.c_str(), &buf) == 0 && buf.st_mode & S_IFREG) {
                //XOSDEBUG("disk stat: %s\n",disk.c_str() );
                diskstat.open(disk.c_str());
                if ( diskstat.good() ) {
                   sec_read=sec_written=0L;
                   diskstat >> dummy >> dummy >> sec_read >> dummy >> dummy >> dummy >> sec_written;

                   sect_size = 512; // XXX: not always true

                   // XXX: ignoring wrap around case for each disk
                   // (would require saving old vals for each disk etc..)
                   all_bytes_read    += (unsigned long long) sec_read * (unsigned long long) sect_size;
                   all_bytes_written += (unsigned long long) sec_written * (unsigned long long) sect_size;

                   //XOSDEBUG("disk stat: %s | read: %ld, written: %ld\n",disk.c_str(),sec_read,sec_written );
                   diskstat.close(); diskstat.clear();
                } else {
                  XOSDEBUG("disk stat open: %s - errno=%d\n",disk.c_str(),errno );
                }
       } else {
        XOSDEBUG("disk stat is not file: %s - errno=%d\n",disk.c_str(),errno );
       }
    } else {
        XOSDEBUG("disk is not dir: %s - errno=%d\n",disk.c_str(),errno );
    }
  } // for
  closedir(dir);
  //XOSDEBUG("disk: read: %ld, written: %ld\n",all_sec_read, all_sec_written );
  update_info(all_bytes_read, all_bytes_written);
}
