/*
   File: abase_meminfo.c
   Code for retrieving and printing the memory usage of the current process.
   Unfortunately, this is very OS dependent code

   Copyright 2005, Radboud University of Nijmegen

   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 2 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   CVS ID: "$Id: abase_meminfo.c,v 1.7 2006/10/09 21:41:46 marcs Exp $"
*/

/* Standard includes */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>

/* local includes */
#include "abase_porting.h"
#include "abase_error.h"
#include "abase_meminfo.h"

#if defined(sun) && (defined(__SVR4) || defined(__svr4__))

/* OS is Solaris */
#include <fcntl.h>
#include <procfs.h>
static void get_memory_info (pid_t pid, int* vm_size, int* rss_size)
{   psinfo_t proc_info;
    char filename[64];
    ssize_t bytes_read;	/* man 2 read */
    int fd;

    /* set defaults */
    *vm_size = -2;
    *rss_size = -2;

    /* try open current process info */
    /* Note pid_t is int on Solaris */
    sprintf (filename, "/proc/%d/psinfo", (int) pid);
    fd = open (filename, O_RDONLY);
    if (fd < 0) return;

    /* try and read proc info structure */
    bytes_read = read (fd, &proc_info, sizeof (psinfo_t));
    close (fd);
    if (bytes_read != sizeof (psinfo_t)) return;

    /* Return the sizes in KB: Solaris sizes are actually size_t */
    *vm_size = (int) proc_info.pr_size;
    *rss_size = (int) proc_info.pr_rssize;
}
#else

#ifdef __linux__

/* OS is Linux */
#include <ctype.h>
#include <sys/user.h>                   /* for PAGE_SHIFT */
static void get_memory_info (pid_t pid, int *vm_size, int *res_size)
{   char proc_fname[64];
    unsigned int vm, res;
    int ix, ch, ok;
    FILE *pfile;

    /* set defaults */
    *vm_size = -2;
    *res_size = -2;

    /* try open current process psinfo */
    /* Note pid_t is int on Linux */
    sprintf (proc_fname, "/proc/%d/stat", (int) pid);
    pfile = fopen (proc_fname, "r");
    if (pfile == NULL)
       { abs_error ("could not open '%s'\n", proc_fname);
         return;
       };

    /* skip 22 fields */
    for (ix = 0; ix < 22; ix++) {
        do
	   { ch = fgetc (pfile);
             if (ch == EOF) return;        /* Too bad */
           }
	while (!isspace (ch));
    }

    /* read vm and rss */
    ok = (fscanf (pfile, "%u", &vm) == 1) ||
         (fscanf (pfile, "%u", &res) == 1);
    fclose (pfile);
    if (!ok) return;

    /* convert them into KB: see man 4 procfs */
    *vm_size = ((int) vm / 1024);
    *res_size = ((int) (res + 3) << (PAGE_SHIFT - 10));  /* note */
};

#else
#ifdef HAVE_LIBKVM

/* OS is some *BSD */
#include <limits.h>
#include <fcntl.h>
#include <kvm.h>
#include <paths.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/user.h>

static void get_memory_info (pid_t pid, int* vm_size, int* res_size)
{   char errbuf[_POSIX2_LINE_MAX];
    struct kinfo_proc *proc_info;
    size_t vm, rss;
    kvm_t *kd;
    int count;

    /* set defaults */
    *vm_size = -2;
    *res_size = -2;

    /* try and open kvm interface */
#if defined(__OpenBSD__) || defined(__NetBSD__)
    kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf);
#else /* assume FreeBSD */
    kd = kvm_openfiles (_PATH_DEVNULL, _PATH_DEVNULL, _PATH_DEVNULL, O_RDONLY, errbuf);
#endif
    if (kd == NULL) return;

    /* ask process info of this process */
    proc_info = kvm_getprocs (kd, KERN_PROC_PID, pid, &count);
    if (count != 1) return;

    /* pick vm and rss_size out of kernel structures */
    /* patch for freeBSD 5.x suggested by  Mathias.Picker@virtual-earth.de */
#if defined(__FreeBSD_version) && __FreeBSD_version >= 500000
    /* FreeBSD > 500000 */
//    vm = proc_info->ki_dsize +
//         proc_info->ki_ssize +
//         proc_info->ki_tsize;
    vm = proc_info -> ki_size;
    rss = proc_info->ki_rssize;
#else
    vm = proc_info -> kp_eproc.e_vm.vm_map.size;
    rss = proc_info -> kp_eproc.e_vm.vm_rssize;
#endif

    kvm_close (kd);

    /* Convert into kilobytes */
    *vm_size = (int) (vm / 1024);
    *res_size = (int) (rss * getpagesize () / 1024);
};
#else /* HAVE_LIBKVM */

/* Unknown OS */
static void get_memory_info (pid_t pid, int* vm_size, int* rss_size)
{
    *vm_size = -1;
    *rss_size = -1;
}
#endif /* HAVE_LIBKVM */
#endif /* __linux__ */
#endif /* sun || svr4 */

void abs_report_meminfo ()
{
#ifndef WIN32
    pid_t my_pid = getpid ();
    int vm_size, rss_size;

    get_memory_info (my_pid, &vm_size, &rss_size);
    if ((vm_size >= 0) && (rss_size >= 0)) {
        abs_message ("memory usage: %dKB (vm), %dKB (rss)", vm_size, rss_size);
    }
#endif /* WIN32 */
}
