/*
 * vmstat(8) - displays virtual memory statistics
 *
 * All new:
 * Robert Love <rml@tech9.net>, 01 Sep 2003
 *
 * Original:
 * Henry Ware <al172@yfn.ysu.edu>, 1994
 *
 * This program is licensed under the GNU General Public License, v2
 * Copyright (C) 2003 Robert Love
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <getopt.h>
#include <sys/ioctl.h>

#include "proc/vmstat.h"
#include "proc/sysinfo.h"
#include "proc/version.h"

static void usage(const char *cmd)
{
	fprintf(stderr, "usage: %s [flags] [delay [count]]\n", cmd);
	fprintf(stderr, "    --noheaders, -n  do not reprint the headers\n");
	fprintf(stderr, "    --active, -a     print active/inactive page "
		"stats\n");
	fprintf(stderr, "    --bytes, -b      print statistics in bytes\n");
	fprintf(stderr, "    --kb, -k         print statistics in KB\n");
	fprintf(stderr, "    --mb, -m         print statistics in MB\n");
	fprintf(stderr, "    --gb, -g         print statistics in GB\n");
	fprintf(stderr, "    --version, -V    print version and exit\n");
	fprintf(stderr, "    --help, -h       display this help and exit\n\n");
	fprintf(stderr, "    delay is the delay in seconds between each "
		"update\n");
	fprintf(stderr, "    count is the number of updates to display "
		"before exiting\n");
	fprintf(stderr, "    The default delay is zero and count is one\n");
}

static int get_term_rows(void)
{
	struct winsize ws;
	int rows = 24;

	if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_row > 0)
		rows = ws.ws_row;

	return rows;
}

static void show_header(int show_active)
{
	printf("%5s%28s%10s%12s%11s%12s\n", "procs", "memory", "swap", "io",
		"system", "cpu");
	printf("%2s %2s %6s %6s %6s %6s %4s %4s %5s %5s %4s %5s %2s "
		"%2s %2s %2s\n", "r", "b", "swpd", "free",
		(show_active) ? "inact" : "buff",
		(show_active) ? "active" : "cache", "si", "so", "bi", "bo",
		"in", "cs", "us", "sy", "wa", "id");
}

int main(int argc, char *argv[])
{
	struct vmstat stats[2] = { VMSTAT_ZERO, VMSTAT_ZERO };
	struct meminfo_struct mem_info = MEMINFO_ZERO;
	long hz;
	unsigned long cpu_user, cpu_system, cpu_idle, cpu_iowait;
	unsigned long total_cpu, half_cpu;
	unsigned int toggle = 0;
	unsigned int i;
	unsigned int count = 0, delay = 1, half_delay;
	int shift = 10, rows = 22, show_active = 0, show_headers = 1;
	int page_units, opt;

	struct option longopts[] = {
		{ "noheaders",	0, NULL, 'n' },
		{ "active",	0, NULL, 'a' },
		{ "bytes",      0, NULL, 'b' },
		{ "kb",		0, NULL, 'k' },
		{ "mb",		0, NULL, 'm' },
		{ "gb",		0, NULL, 'g' },
		{ "version",	0, NULL, 'V' },
		{ "help",	0, NULL, 'h' },
		{ NULL,		0, NULL, 0 }
	};

	setlinebuf(stdout);

	while ((opt = getopt_long(argc, argv, "nabkmgVh", longopts,
			NULL)) != -1) {
		switch (opt) {
		case 'n':
			show_headers = 0;
			break;
		case 'a':
			show_active =1;
			break;
		case 'b':
			shift = 0;
			break;
		case 'k':
			shift = 10;
			break;
		case 'm':
			shift = 20;
			break;
		case 'g':
			shift = 30;
			break;
		case 'V':
			display_version();
			return 0;
		case 'h':
			usage(argv[0]);
			return 0;
		default:
			usage(argv[0]);
			return 1;
		}
	}

	if (argc - optind > 0) {
		errno = 0;
		delay = strtoul(argv[optind], NULL, 10);
		if (errno) {
			perror("strtoul");
			return 1;
		}
		count = ULONG_MAX;	/* XXX: this is gross */
		if (argc - optind > 1) {
			errno = 0;
			count = strtoul(argv[optind+1], NULL, 10);
			if (errno) {
				perror("strtoul");
				return 1;
			}
		}
	}

	if (show_headers) {
		int tmp = get_term_rows() - 3;
		rows = ((tmp > 0) ? tmp : 22);
	}
	show_header(show_active);

	meminfo(&mem_info);
	if (mem_info.mem.total == 0) {
		fprintf(stderr, "error: unable to parse /proc/meminfo\n");
		return 1;
	}
	if (get_vmstats(stats))
		return 1;
	stats[0].running--;	/* we don't want to count ourselves */
	cpu_user = stats[0].cpu_user + stats[0].cpu_nice;
	cpu_system = stats[0].cpu_system;
	cpu_idle = stats[0].cpu_idle;
	cpu_iowait = stats[0].cpu_iowait;
	total_cpu = cpu_user + cpu_system + cpu_iowait + cpu_idle;
	if (!total_cpu)
		total_cpu = 1;
	half_delay = delay / 2;
	page_units = getpagesize() >> shift;
	hz = sysconf(_SC_CLK_TCK);	/* get tick rate from system */
	if (hz < 0) {
		perror("sysconf");
		return 1;
	}
	half_cpu = total_cpu / 2;

	printf("%2lu %2lu %6llu %6llu %6llu %6llu %4lu %4lu %5lu %5lu "
		"%4lu %5lu %2lu %2lu %2lu %2lu\n",
		stats[0].running, stats[0].blocked,
		mem_info.swap.used >> shift, mem_info.mem.free >> shift,
		(show_active) ? (mem_info.mem.inactive_dirty +
				mem_info.mem.inactive_clean) >> shift :
				mem_info.mem.buffers >> shift,
		(show_active) ? mem_info.mem.active >> shift :
				mem_info.mem.cached >> shift,
		(stats[0].swap_in * page_units * hz + half_cpu) / total_cpu,
		(stats[0].swap_out * page_units * hz + half_cpu) / total_cpu,
		(stats[0].block_in * hz + half_cpu) / total_cpu,
		(stats[0].block_out * hz + half_cpu) / total_cpu,
		(stats[0].interrupts * hz + half_cpu) / total_cpu,
		(stats[0].ctxt * hz + half_cpu) / total_cpu,
		(100 * cpu_user + half_cpu) / total_cpu,
		(100 * cpu_system + half_cpu) / total_cpu,
		(100 * cpu_iowait + half_cpu) / total_cpu,
		(100 * cpu_idle + half_cpu) / total_cpu);

	for (i = 1; i < count; i++) {
		struct vmstat *prev, *current;

		toggle = !toggle;
		current = stats + toggle;
		prev = stats + !toggle;

		sleep(delay);
		if (show_headers && ((i % rows) == 0))
			show_header(show_active);

		meminfo(&mem_info);
		if (mem_info.mem.total == 0) {
			fprintf(stderr, "error: unable to parse "
					"/proc/meminfo\n");
			return 1;
		}
		if (get_vmstats(current))
			return 1;
		current->running--; /* we don't want to count ourselves */

		cpu_user = current->cpu_user - prev->cpu_user +
			current->cpu_nice - prev->cpu_nice;
		cpu_system = current->cpu_system - prev->cpu_system;
		cpu_iowait = current->cpu_iowait - prev->cpu_iowait;
		cpu_idle = prev->cpu_idle <= current->cpu_idle ?
			(current->cpu_idle - prev->cpu_idle) % UINT_MAX : 0;
		total_cpu = cpu_user + cpu_system + cpu_iowait + cpu_idle;
		if (!total_cpu)
			total_cpu = 1;
		half_cpu = total_cpu / 2;

		printf("%2lu %2lu %6llu %6llu %6llu %6llu %4lu %4lu %5lu "
			"%5lu %4lu %5lu %2lu %2lu %2lu %2lu\n",
			current->running, current->blocked,
			mem_info.swap.used >> shift, mem_info.mem.free >> shift,
			(show_active) ? (mem_info.mem.inactive_dirty +
				mem_info.mem.inactive_clean) >> shift :
				mem_info.mem.buffers >> shift,
			(show_active) ? mem_info.mem.active >> shift :
				mem_info.mem.cached >> shift,
			(((current->swap_in - prev->swap_in) * page_units +
				half_delay) / delay),
			(((current->swap_out - prev->swap_out) * page_units +
				half_delay) / delay),
			(current->block_in - prev->block_in + half_delay) /
				delay,
			(current->block_out - prev->block_out + half_delay) /
				delay,
			(current->interrupts - prev->interrupts + half_delay) /
				delay,
			(current->ctxt - prev->ctxt + half_delay) / delay,
			(100 * cpu_user + half_cpu) / total_cpu,
			(100 * cpu_system + half_cpu) / total_cpu,
			(100 * cpu_iowait + half_cpu) / total_cpu,
			(100 * cpu_idle + half_cpu) / total_cpu);
	}

	return 0;
}
