/*
 * Copyright (C), 2000-2007 by the monit project group.
 * All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */


#include <config.h>

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef TIME_WITH_SYS_TIME
#include <time.h>

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#else
#include <time.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_ASM_PARAM_H
#include <asm/param.h>
#endif

#ifdef HAVE_GLOB_H
#include <glob.h>
#endif

#ifndef HZ
# define HZ sysconf(_SC_CLK_TCK)
#endif

#include "monitor.h"
#include "process.h"
#include "process_sysdep.h"


/**
 *  System dependent resource gathering code for Linux.
 *
 *  @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
 *  @author Christian Hopp <chopp@iei.tu-clausthal.de>
 *  @author Martin Pala <martinp@tildeslash.com>
 *  @author Arkadiusz Miskiewicz <arekm@pld-linux.org>
 *
 *  @version \$Id: sysdep_LINUX.c,v 1.44 2007/07/25 12:54:32 hauk Exp $
 *
 *  @file
 */


/* ----------------------------------------------------------------- Private */


#define MEMTOTAL "MemTotal:"
#define MEMFREE  "MemFree:"
#define MEMBUF   "Buffers:"
#define MEMCACHE "Cached:"

static unsigned long long old_cpu_user     = 0;
static unsigned long long old_cpu_syst     = 0;
static unsigned long long old_cpu_wait     = 0;
static unsigned long long old_cpu_total    = 0;
static int                page_shift_to_kb = 0;


/* ------------------------------------------------------------------ Public */


int init_process_info_sysdep(void) {
  char *ptr;
  char  buf[1024];
  long  page_size;
  int   page_shift;  

  if(!read_proc_file(buf, 1024, "meminfo", -1)) 
  {
    return FALSE;
  }
  if(!(ptr= strstr(buf, MEMTOTAL)))
  {
    DEBUG("system statistic error -- cannot get real memory amount\n");
    return FALSE;
  }
  if(sscanf(ptr+strlen(MEMTOTAL), "%ld", &systeminfo.mem_kbyte_max) != 1)
  {
    DEBUG("system statistic error -- cannot get real memory amount\n");
    return FALSE;
  }

  if((num_cpus = sysconf(_SC_NPROCESSORS_CONF)) == -1)
  {
    DEBUG("system statistic error -- cannot get cpu count: %s\n", STRERROR);
    return FALSE;
  }

  if((page_size = sysconf(_SC_PAGESIZE)) <= 0) {
    DEBUG("system statistic error -- cannot get page size: %s\n", STRERROR);
    return FALSE;
  }

  for(page_shift=0; page_size!=1; page_size>>=1, page_shift++);
  page_shift_to_kb=page_shift-10;

  return TRUE;
}


/**
 * Read all processes of the proc files system to initialize
 * the process tree (sysdep version... but should work for
 * all procfs based unices) 
 * @param reference  reference of ProcessTree
 * @return treesize>0 if succeeded otherwise =0.
 */
int initprocesstree_sysdep(ProcessTree_T ** reference) {
  int            i = 0;
  int            treesize = 0;
  int            stat_ppid = 0;
  char          *tmp = NULL;
  char           buf[4096];
  char           stat_item_state;
  long           stat_item_cutime = 0;
  long           stat_item_cstime = 0;
  long           stat_item_rss = 0;
  glob_t         globbuf;
  unsigned long  stat_item_utime = 0;
  unsigned long  stat_item_stime = 0;
  ProcessTree_T *pt = NULL;

  ASSERT(reference);

  /* Find all processes in the /proc directory */
  if(glob("/proc/[0-9]*", GLOB_ONLYDIR, NULL, &globbuf))
  {
    LogError("system statistic error -- glob failed\n");
    return FALSE;
  } 

  treesize = globbuf.gl_pathc;

  pt = xcalloc(sizeof(ProcessTree_T), treesize);

  /* Insert data from /proc directory */
  for(i = 0; i < treesize; i++)
  {
    pt[i].pid = atoi(globbuf.gl_pathv[i] + strlen("/proc/"));
    
    if(!read_proc_file(buf, 4096, "stat", pt[i].pid))
    {
      DEBUG(
        "system statistic error -- cannot read /proc/%d/stat\n",
        pt[i].pid);
      continue;
    }

    pt[i].time = get_float_time();

    /* Move along the buffer to get past the process name */
    if(!(tmp = strrchr(buf, ')')))
    {
      DEBUG(
        "system statistic error -- file /proc/%d/stat parse error\n",
        pt[i].pid);
      continue;
    }

    tmp += 2;

    /* This implementation is done by using fs/procfs/array.c as a basis
     * it is also worth looking into the source of the procps utils */
    if(sscanf(tmp,
         "%c %d %*d %*d %*d %*d %*u %*u"
         "%*u %*u %*u %lu %lu %ld %ld %*d %*d %*d "
         "%*d %*u %*u %ld %*u %*u %*u %*u %*u "
         "%*u %*u %*u %*u %*u %*u %*u %*u %*d %*d\n",
         &stat_item_state,
         &stat_ppid,
         &stat_item_utime,
         &stat_item_stime,
         &stat_item_cutime,
         &stat_item_cstime,
         &stat_item_rss) != 7)
    {
      DEBUG(
        "system statistic error -- file /proc/%d/stat parse error\n",
        pt[i].pid);
      continue;
    }
    
    /* abs to please the compiler... we dont want to shift negatively.
     * why doesn't C understand this??? */
    pt[i].ppid = stat_ppid;
  
    /* jiffies -> seconds = 1 / HZ
     * HZ is defined in "asm/param.h"  and it is usually 1/100s but on
     * alpha system it is 1/1024s */
    pt[i].cputime =
        ((float)(stat_item_utime + stat_item_stime) * 10.0) / HZ;

    /* State is Zombie -> then we are a Zombie ... clear or? (-: */
    if(stat_item_state == 'Z')
    {
      pt[i].status_flag |= PROCESS_ZOMBIE;
    }

    if(page_shift_to_kb < 0)
    {
      pt[i].mem_kbyte = (stat_item_rss >> abs(page_shift_to_kb));
    }
    else
    {
      pt[i].mem_kbyte = (stat_item_rss << abs(page_shift_to_kb));
    }
  }
  
  *reference = pt;
  globfree(&globbuf);

  return treesize;
}


/**
 * This routine returns 'nelem' double precision floats containing
 * the load averages in 'loadv'; at most 3 values will be returned.
 * @param loadv destination of the load averages
 * @param nelem number of averages
 * @return: 0 if successful, -1 if failed (and all load averages are 0).
 */
int getloadavg_sysdep (double *loadv, int nelem) {
  return getloadavg(loadv, nelem);
}


/**
 * This routine returns kbyte of real memory in use.
 * @return: TRUE if successful, FALSE if failed
 */
int used_system_memory_sysdep(SystemInfo_T *si) {
  char          *ptr;
  char           buf[1024];
  unsigned long  mem_free;
  unsigned long  buffers;
  unsigned long  cached;
  
  if(!read_proc_file(buf, 1024, "meminfo", -1))
  {
    LogError("system statistic error -- cannot get real memory free amount\n");
    goto error;
  }

  if(!(ptr= strstr(buf, MEMFREE)))
  {
    LogError("system statistic error -- cannot get real memory free amount\n");
    goto error;
  }
  if(sscanf(ptr + strlen(MEMFREE), "%ld", &mem_free) != 1)
  {
    LogError("system statistic error -- cannot get real memory free amount\n");
    goto error;
  }

  if(!(ptr= strstr(buf, MEMBUF)))
  {
    LogError("system statistic error -- cannot get real memory buffers "
      "amount\n");
    goto error;
  }
  if(sscanf(ptr + strlen(MEMBUF), "%ld", &buffers) != 1)
  {
    LogError("system statistic error -- cannot get real memory buffers "
      "amount\n");
    goto error;
  }

  if(!(ptr= strstr(buf, MEMCACHE)))
  {
    LogError("system statistic error -- cannot get real memory cache amount\n");
    goto error;
  }
  if(sscanf(ptr + strlen(MEMCACHE), "%ld", &cached) != 1)
  {
    LogError("system statistic error -- cannot get real memory cache free "
      "amount\n");
    goto error;
  }

  si->total_mem_kbyte = systeminfo.mem_kbyte_max - mem_free - buffers - cached;

  if(si->total_mem_kbyte < 0)
  {
    LogError("system statistic error -- memory usage statistic error\n");
    goto error;
  }

  return TRUE;

  error:
  LogError("system statistic error -- memory usage gathering failed\n");
  si->total_mem_kbyte = 0;
  return FALSE;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: TRUE if successful, FALSE if failed (or not available)
 */
int used_system_cpu_sysdep(SystemInfo_T *si) {
  int                rv;
  unsigned long long cpu_total;
  unsigned long long cpu_user;
  unsigned long long cpu_nice;
  unsigned long long cpu_syst;
  unsigned long long cpu_idle;
  unsigned long long cpu_wait;
  unsigned long long cpu_irq;
  unsigned long long cpu_softirq;
  char   buf[1024];

  if(!read_proc_file(buf, 1024, "stat", -1))
  {
    LogError("system statistic error -- cannot read /proc/stat\n");
    goto error;
  }

  rv = sscanf(buf, "cpu %llu %llu %llu %llu %llu %llu %llu",
         &cpu_user,
         &cpu_nice,
         &cpu_syst,
         &cpu_idle,
         &cpu_wait,
         &cpu_irq,
         &cpu_softirq);
  if(rv < 4)
  {
    LogError("system statistic error -- cannot read cpu usage\n");
    goto error;
  }
  else if(rv == 4)
  {
    /* linux 2.4.x doesn't support these values */
    cpu_wait = 0;
    cpu_irq = 0;
    cpu_softirq = 0;
  }

  cpu_total= cpu_user + cpu_nice + cpu_syst + cpu_idle + cpu_wait + cpu_irq + cpu_softirq;
  cpu_user = cpu_user + cpu_nice;

  if(old_cpu_total == 0)
  {
    si->total_cpu_user_percent = 0;
    si->total_cpu_syst_percent = 0;
    si->total_cpu_wait_percent = 0;
  }
  else
  {
    unsigned long long delta = cpu_total - old_cpu_total;
  
    si->total_cpu_user_percent =
      (int)(1000 * (double)(cpu_user - old_cpu_user) / delta);
    si->total_cpu_syst_percent =
      (int)(1000 * (double)(cpu_syst - old_cpu_syst) / delta);
    si->total_cpu_wait_percent =
      (int)(1000 * (double)(cpu_wait - old_cpu_wait) / delta);
  }

  old_cpu_user = cpu_user;
  old_cpu_syst = cpu_syst;
  old_cpu_wait = cpu_wait;
  old_cpu_total= cpu_total;
  return TRUE;

  error:
  si->total_cpu_user_percent = 0;
  si->total_cpu_syst_percent = 0;
  si->total_cpu_wait_percent = 0;
  return FALSE;
}



syntax highlighted by Code2HTML, v. 0.9.1