/*
 * proc/vmstat.c - libproc interface for /proc/stat and /proc/vmstat
 *
 * Robert Love <rml@tech9.net>, 01 Sep 2003
 *
 * This library is licensed under the GNU Library General Public License, v2.1
 * Copyright (C) 2003 Robert Love
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/dir.h>

#include "proc/vmstat.h"

#define PIDSTAT_BUF_LEN	1024
#define STAT_BUF_LEN	2048
#define VMSTAT_BUF_LEN	1024

/*
 * getrunners - calculate and return the running and blocked fields
 *
 * This is only needed on pre-2.5 kernels, because they do not export
 * procs_running and procs_blocked in /proc/stat.  Therefore, this
 * function needs to read each process's stat file and tally everything
 * up.  This is O(n) and gross, whereas under a 2.5 or later kernel we
 * can just read the values in constant-time.  The code in procstat()
 * will fall back automatically if needed.
 *
 * Returns zero on success; nonzero on failure.  On success, stats->running
 * and stats->blocked are valid.
 */
static int getrunners(struct vmstat *stats)
{
	struct direct *ent;
	DIR *proc;
	unsigned long running = 0, blocked = 0;

	proc = opendir("/proc");
	if (!proc) {
		perror("opendir");
		return 1;
	}

	while ((ent = readdir(proc))) {
		/* just to weed out the obvious junk */
		if (isdigit(ent->d_name[0])) {
			char filename[PATH_MAX];
			int fd;

			snprintf(filename, PATH_MAX, "/proc/%s/stat",
					ent->d_name);
			/* this weeds out the rest */
			if ((fd = open(filename, O_RDONLY, 0)) != -1) {
				char buf[PIDSTAT_BUF_LEN];
				char c;
	
				if (read(fd, buf, PIDSTAT_BUF_LEN - 1) < 0) {
					perror("read");
					return 1;
				}
				close(fd);
				sscanf(buf, "%*d %*s %c %*d %*d %*d %*d %*d "
					"%*u %*u %*u %*u %*u %*d %*d %*d %*d "
					"%*d %*d %*u %*u %*d %*u %*u %*u %*u "
					"%*u %*u %*u %*u %*u %*u %*u %*u %*u\n",
					&c);

				if (c == 'R')
					running++;
				else if (c == 'D')
					blocked++;
			} else {
				perror("open");
				return 1;
			}
		}
	}
	closedir(proc);

	stats->running = running;
	stats->blocked = blocked;

	return 0;
}

/*
 * procstat - parse /proc/stat for the relevant items for struct vmstat
 *
 * Returns zero on success; nonzero on failure.  On success, the following
 * struct vmstat values are filled out: cpu_nice, cpu_system, cpu_idle,
 * cpu_iowait (>=2.5), block_in (<2.5), block_out (<2.5), swap_in (<2.5),
 * swap_out (<2.5), interrupts, ctxt, running (>=2.5), blocked (>=2.5).
 */
static int procstat(struct vmstat *stats)
{
	char buf[STAT_BUF_LEN];
	char *p;
	int fd;

	fd = open("/proc/stat", O_RDONLY);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	if (read(fd, buf, STAT_BUF_LEN-1) < 1) {
		perror("read");
		return 1;
	}
	buf[STAT_BUF_LEN-1] = '\0';
	close(fd);

	p = strstr(buf, "cpu ");
	if (p)
		sscanf(p, "cpu  %lu %lu %lu %lu %lu", &(stats->cpu_user),
			&(stats->cpu_nice), &(stats->cpu_system),
			&(stats->cpu_idle), &(stats->cpu_iowait));
	p = strstr(buf, "page ");
	if (p)
		sscanf(p, "page %lu %lu", &(stats->block_in),
			&(stats->block_out));
	p = strstr(buf, "swap ");
	if (p)
		sscanf(p, "swap %lu %lu", &(stats->swap_in),
			&(stats->swap_out));
	p = strstr(buf, "intr ");
	if (p)
		sscanf(p, "intr %lu", &(stats->interrupts));
	p = strstr(buf, "ctxt ");
	if (p)
		sscanf(p, "ctxt %lu", &(stats->ctxt));
	p = strstr(buf, "procs_running ");
	if (p) {
		sscanf(p, "procs_running %lu", &(stats->running));
		p = strstr(buf, "procs_blocked ");
		if (p)
			sscanf(p, "procs_blocked %lu", &(stats->blocked));
	} else
		return getrunners(stats);

	return 0;
}

/*
 * procvmstat - parse /proc/vmstat and fill out struct vmstat
 *
 * This is only needed by 2.5 and later kernels.  Earlier kernels get
 * all of this information from /proc/stat and /proc/vmstat does not
 * exist.
 *
 * Returns zero on success; nonzero on failure.  On success block_in,
 * block_out, swap_in, and swap_out are valid.  Note that the function
 * also returns success if the /proc/vmstat file could not be opened.
 * In that case, the structure is not touched and it is assumed that a
 * previous call to procstat() filled them out.
 */
static int procvmstat(struct vmstat *stats)
{
	char buf[VMSTAT_BUF_LEN];
	char *p;
	int fd;

	fd = open("/proc/vmstat", O_RDONLY);
	if (fd < 0)
		return 0; /* only exists on >=2.5 kernels */

	if (read(fd, buf, VMSTAT_BUF_LEN) < 1) {
		perror("read");
		return 1;
	}
	buf[VMSTAT_BUF_LEN - 1] = '\0';
	close(fd);

	p = strstr(buf, "pgpgin ");
	if (p)
		sscanf(p, "pgpgin %lu", &(stats->block_in));
	p = strstr(buf, "pgpgout ");
	if (p)
		sscanf(p, "pgpgout %lu", &(stats->block_out));
	p = strstr(buf, "pswblock_in ");
	if (p)
		sscanf(p, "pswblock_in %lu", &(stats->swap_in));
	p = strstr(buf, "pswblock_out ");
	if (p)
		sscanf(p, "pswblock_out %lu", &(stats->swap_out));

	return 0;
}

/*
 * get_vmstats - fill out a vmstat structure
 *
 * Returns zero on success; nonzero on failure.
 * This is the main exported interface for vmstat.
 */
int get_vmstats(struct vmstat *stats)
{
	if (procstat(stats)) {
		fprintf(stderr, "error: failed to parse /proc/stats\n");
		return 1;
	}
	if (procvmstat(stats)) {
		fprintf(stderr, "error: failed to parse /proc/vmstat\n");
		return 1;
	}
	return 0;
}
