/*
 * nfsiostat-sysstat: Report NFS I/O statistics
 * Copyright (C) 2010 Red Hat, Inc. All Rights Reserved
 * Written by Ivana Varekova <varekova@redhat.com>
 *
 ***************************************************************************
 * 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 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, write to the Free Software Foundation, Inc., *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                   *
 ***************************************************************************
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/utsname.h>

#include "version.h"
#include "nfsiostat-sysstat.h"
#include "count.h"
#include "common.h"

#ifdef USE_NLS
#include <locale.h>
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

#define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
char *sccsid(void) { return (SCCSID); }

unsigned long long uptime[2]  = {0, 0};
unsigned long long uptime0[2] = {0, 0};
struct io_nfs_stats *st_ionfs[2];
struct io_hdr_stats *st_hdr_ionfs;

int ionfs_nr = 0;	/* Nb of NFS mounted directories found */
int cpu_nr = 0;		/* Nb of processors on the machine */
int flags = 0;		/* Flag for common options and system state */

long interval = 0;
char timestamp[64];

struct sigaction alrm_act;

/*
 ***************************************************************************
 * Print usage and exit.
 *
 * IN:
 * @progname	Name of sysstat command.
 ***************************************************************************
 */
void usage(char *progname)
{
	fprintf(stderr, _("Usage: %s [ options ] [ <interval> [ <count> ] ]\n"),
		progname);

#ifdef DEBUG
	fprintf(stderr, _("Options are:\n"
			  "[ -h ] [ -k | -m ] [ -t ] [ -V ] [ --debuginfo ]\n"));
#else
	fprintf(stderr, _("Options are:\n"
			  "[ -h ] [ -k | -m ] [ -t ] [ -V ]\n"));
#endif
	exit(1);
}

/*
 ***************************************************************************
 * Set output unit. Unit will be kB/s unless POSIXLY_CORRECT
 * environment variable has been set, in which case the output will be
 * expressed in blocks/s.
 ***************************************************************************
 */
void set_output_unit(void)
{
	char *e;

	if (DISPLAY_KILOBYTES(flags) || DISPLAY_MEGABYTES(flags))
		return;

	/* Check POSIXLY_CORRECT environment variable */
	if ((e = getenv(ENV_POSIXLY_CORRECT)) == NULL) {
		/* Variable not set: Unit is kB/s and not blocks/s */
		flags |= I_D_KILOBYTES;
	}
}

/*
 ***************************************************************************
 * SIGALRM signal handler.
 *
 * IN:
 * @sig	Signal number.
 ***************************************************************************
 */
void alarm_handler(int sig)
{
	alarm(interval);
}

/*
 ***************************************************************************
 * Find number of NFS-mounted points that are registered in
 * /proc/self/mountstats.
 *
 * RETURNS:
 * Number of NFS-mounted points.
 ***************************************************************************
 */
int get_nfs_mount_nr(void)
{
	FILE *fp;
	char line[8192];
	char type_name[10];
	unsigned int nfs = 0;

	if ((fp = fopen(NFSMOUNTSTATS, "r")) == NULL)
		/* File non-existent */
		return 0;

	while (fgets(line, sizeof(line), fp) != NULL) {

		if ((strstr(line, "mounted")) && (strstr(line, "on")) &&
		    (strstr(line, "with")) && (strstr(line, "fstype"))) {

			sscanf(strstr(line, "fstype") + 6, "%9s", type_name);
			if ((!strncmp(type_name, "nfs", 3)) && (strncmp(type_name, "nfsd", 4))) {
				nfs ++;
			}
		}
	}

	fclose(fp);

	return nfs;
}

/*
 ***************************************************************************
 * Set every nfs_io entry to inactive state (unregistered).
 ***************************************************************************
 */
void set_entries_inactive(void)
{
	int i;
	struct io_hdr_stats *shi = st_hdr_ionfs;

	for (i = 0; i < ionfs_nr; i++, shi++) {
		shi->active = FALSE;
	}
}

/*
 ***************************************************************************
 * Free inactive entries (mark them as unused).
 ***************************************************************************
 */
void free_inactive_entries(void)
{
	int i;
	struct io_hdr_stats *shi = st_hdr_ionfs;

	for (i = 0; i < ionfs_nr; i++, shi++) {
		if (!shi->active) {
			shi->used = FALSE;
		}
	}
}

/*
 ***************************************************************************
 * Allocate and init structures, according to system state.
 ***************************************************************************
 */
void io_sys_init(void)
{
	int i;

	/* How many processors on this machine? */
	cpu_nr = get_cpu_nr(~0, FALSE);

	/* Get number of NFS directories in /proc/self/mountstats */
	if ((ionfs_nr = get_nfs_mount_nr()) > 0) {
		ionfs_nr += NR_NFS_PREALLOC;
	}
	if ((st_hdr_ionfs = (struct io_hdr_stats *) calloc(ionfs_nr, IO_HDR_STATS_SIZE)) == NULL) {
		perror("malloc");
		exit(4);
	}

	/* Allocate structures for number of NFS directories found */
	for (i = 0; i < 2; i++) {
		if ((st_ionfs[i] =
		    (struct io_nfs_stats *) calloc(ionfs_nr, IO_NFS_STATS_SIZE)) == NULL) {
			perror("malloc");
			exit(4);
		}
	}
}

/*
 ***************************************************************************
 * Free various structures.
 ***************************************************************************
*/
void io_sys_free(void)
{
	int i;

	/* Free I/O NFS directories structures */
	for (i = 0; i < 2; i++) {
		free(st_ionfs[i]);
	}

	free(st_hdr_ionfs);
}

/*
 ***************************************************************************
 * Save stats for current NFS filesystem.
 *
 * IN:
 * @name		Name of NFS filesystem.
 * @curr		Index in array for current sample statistics.
 * @st_io		Structure with NFS statistics to save.
 * @ionfs_nr		Number of NFS filesystems.
 * @st_hdr_ionfs	Pointer on structures describing an NFS filesystem.
 *
 * OUT:
 * @st_hdr_ionfs	Pointer on structures describing an NFS filesystem.
 ***************************************************************************
 */
void save_stats(char *name, int curr, void *st_io)
{
	int i, j;
	struct io_hdr_stats *st_hdr_ionfs_i;
	struct io_nfs_stats *st_ionfs_i;

	/* Look for NFS directory in data table */
	for (i = 0; i < ionfs_nr; i++) {
		st_hdr_ionfs_i = st_hdr_ionfs + i;
		if ((st_hdr_ionfs_i->used) &&
		    (!strcmp(st_hdr_ionfs_i->name, name))) {
			break;
		}
	}

	if (i == ionfs_nr) {
		/*
		 * This is a new filesystem: Look for an unused entry to store it.
		 */
		for (i = 0; i < ionfs_nr; i++) {
			st_hdr_ionfs_i = st_hdr_ionfs + i;
			if (!st_hdr_ionfs_i->used) {
				/* Unused entry found... */
				st_hdr_ionfs_i->used = TRUE; /* Indicate it is now used */
				st_hdr_ionfs_i->active = TRUE;

				strcpy(st_hdr_ionfs_i->name, name);
				st_ionfs_i = st_ionfs[curr] + i;
				memset(st_ionfs_i, 0, IO_NFS_STATS_SIZE);
				*st_ionfs_i = *((struct io_nfs_stats *) st_io);
				break;
			}
		}
		if (i == ionfs_nr) {
			/* All entries are used: The number has to be increased */
			ionfs_nr = ionfs_nr + 5;

			/* Increase the size of st_hdr_ionfs buffer */
			if ((st_hdr_ionfs = (struct io_hdr_stats *)
				realloc(st_hdr_ionfs, ionfs_nr * IO_HDR_STATS_SIZE)) == NULL) {
				perror("malloc");
				exit(4);
			}

			/* Set the new entries inactive */
			for (j = 0; j < 5; j++) {
				st_hdr_ionfs_i = st_hdr_ionfs + i + j;
				st_hdr_ionfs_i->used = FALSE;
				st_hdr_ionfs_i->active = FALSE;
			}

			/* Increase the size of st_hdr_ionfs buffer */
			for (j = 0; j < 2; j++) {
				if ((st_ionfs[j] = (struct io_nfs_stats *)
					realloc(st_ionfs[j], ionfs_nr * IO_NFS_STATS_SIZE)) == NULL) {
					perror("malloc");
					exit(4);
				}
				memset(st_ionfs[j] + i, 0, 5 * IO_NFS_STATS_SIZE);
			}

			/* Now i shows the first unused entry of the new block */
			st_hdr_ionfs_i = st_hdr_ionfs + i;
			st_hdr_ionfs_i->used = TRUE; /* Indicate it is now used */
			strcpy(st_hdr_ionfs_i->name, name);
			st_ionfs_i = st_ionfs[curr] + i;
			memset(st_ionfs_i, 0, IO_NFS_STATS_SIZE);
		}
	} else  {
		st_hdr_ionfs_i = st_hdr_ionfs + i;
		st_hdr_ionfs_i->used = TRUE;
		st_hdr_ionfs_i->active = TRUE;
		st_ionfs_i = st_ionfs[curr] + i;
		*st_ionfs_i = *((struct io_nfs_stats *) st_io);
	}
	/*
	 * else it was a new NFS directory
	 * but there was no free structure to store it.
	 */
}

/*
 ***************************************************************************
 * Read NFS-mount directories stats from /proc/self/mountstats.
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 ***************************************************************************
 */
void read_nfs_stat(int curr)
{
	FILE *fp;
	int sw = 0;
	char line[256];
	char *xprt_line;
	char *mount_part;
	char nfs_name[MAX_NAME_LEN];
	char mount[10], on[10], prefix[10], aux[32];
	char operation[16];
	struct io_nfs_stats snfs;
	long int v1;

	/* Every I/O NFS entry is potentially unregistered */
	set_entries_inactive();

	if ((fp = fopen(NFSMOUNTSTATS, "r")) == NULL)
		return;

	sprintf(aux, "%%%ds",
		MAX_NAME_LEN < 200 ? MAX_NAME_LEN-1 : 200);

	while (fgets(line, sizeof(line), fp) != NULL) {
		/* Read NFS directory name */
		if (!strncmp(line, "device", 6)) {
			sw = 0;
			sscanf(line + 6, aux, nfs_name);
			mount_part = strchr(line + 7, ' ');
			if (mount_part != NULL) {
				sscanf(mount_part, "%9s %9s", mount, on);
				if ((!strncmp(mount, "mounted", 7)) && (!strncmp(on, "on", 2))) {
					sw = 1;
				}
			}
		}

		sscanf(line, "%9s", prefix);
		if (sw && (!strncmp(prefix, "bytes:", 6))) {
			/* Read the stats for the last NFS-mounted directory */
			sscanf(strstr(line, "bytes:") + 6, "%llu %llu %llu %llu %llu %llu",
			       &snfs.rd_normal_bytes, &snfs.wr_normal_bytes,
			       &snfs.rd_direct_bytes, &snfs.wr_direct_bytes,
			       &snfs.rd_server_bytes, &snfs.wr_server_bytes);
			sw = 2;
		}

		if ((sw == 2) && (!strncmp(prefix, "xprt:", 5))) {
			/*
			 * Read extended statistic for the last NFS-mounted directory
			 * - number of sent rpc requests.
			 */
			xprt_line = (strstr(line, "xprt:") + 6);
			/* udp, tcp or rdma data */
			if (!strncmp(xprt_line, "udp", 3)) {
				/* port bind_count sends recvs (bad_xids req_u bklog_u) */
				sscanf(strstr(xprt_line, "udp") + 4, "%*u %*u %lu",
				       &snfs.rpc_sends);
			}
			if (!strncmp(xprt_line, "tcp", 3)) {
				/*
				 * port bind_counter connect_count connect_time idle_time
				 * sends recvs (bad_xids req_u bklog_u)
				 */
				sscanf(strstr(xprt_line, "tcp") + 4,
				       "%*u %*u %*u %*u %*d %lu",
				       &snfs.rpc_sends);
			}
			if (!strncmp(xprt_line,"rdma", 4)) {
				/*
				 * 0(port) bind_count connect_count connect_time idle_time
				 * sends recvs (bad_xids req_u bklog_u...)
				 */
				sscanf(strstr(xprt_line, "rdma") + 5,
				       "%*u %*u %*u %*u %*d %lu",
				       &snfs.rpc_sends);
			}
			sw = 3;
		}

		if ((sw == 3) && (!strncmp(prefix, "per-op", 6))) {
			sw = 4;
			while (sw == 4) {
				if (fgets(line, sizeof(line), fp) == NULL)
					break;
				sscanf(line, "%15s %lu", operation, &v1);
				if (!strncmp(operation, "READ:", 5)) {
					snfs.nfs_rops = v1;
				}
				else if (!strncmp(operation, "WRITE:", 6)) {
					snfs.nfs_wops = v1;

					save_stats(nfs_name, curr, &snfs);
					sw = 0;
				}
			}
		}
	}

	fclose(fp);

	/* Free structures corresponding to unregistered filesystems */
	free_inactive_entries();
}

/*
 ***************************************************************************
 * Display NFS stats header.
 *
 * OUT:
 * @fctr	Conversion factor.
 ***************************************************************************
 */
void write_nfs_stat_header(int *fctr)
{
	printf("Filesystem:           ");
	if (DISPLAY_KILOBYTES(flags)) {
		printf("    rkB_nor/s    wkB_nor/s    rkB_dir/s    wkB_dir/s"
		       "    rkB_svr/s    wkB_svr/s");
		*fctr = 1024;
	}
	else if (DISPLAY_MEGABYTES(flags)) {
		printf("    rMB_nor/s    wMB_nor/s    rMB_dir/s    wMB_dir/s"
		       "    rMB_svr/s    wMB_svr/s");
		*fctr = 1024 * 1024;
	}
	else {
		printf("   rBlk_nor/s   wBlk_nor/s   rBlk_dir/s   wBlk_dir/s"
		       "   rBlk_svr/s   wBlk_svr/s");
		*fctr = 512;
	}
	printf("     ops/s    rops/s    wops/s\n");
}

/*
 ***************************************************************************
 * Write NFS stats read from /proc/self/mountstats.
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @itv		Interval of time.
 * @fctr	Conversion factor.
 * @shi		Structures describing the NFS filesystems.
 * @ioi		Current sample statistics.
 * @ioj		Previous sample statistics.
 ***************************************************************************
 */
void write_nfs_stat(int curr, unsigned long long itv, int fctr,
		    struct io_hdr_stats *shi, struct io_nfs_stats *ioni,
		    struct io_nfs_stats *ionj)
{
	if (DISPLAY_HUMAN_READ(flags)) {
		printf("%-22s\n%23s", shi->name, "");
	}
	else {
		printf("%-22s ", shi->name);
	}
	printf("%12.2f %12.2f %12.2f %12.2f %12.2f %12.2f %9.2f %9.2f %9.2f\n",
	       S_VALUE(ionj->rd_normal_bytes, ioni->rd_normal_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_normal_bytes, ioni->wr_normal_bytes, itv) / fctr,
	       S_VALUE(ionj->rd_direct_bytes, ioni->rd_direct_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_direct_bytes, ioni->wr_direct_bytes, itv) / fctr,
	       S_VALUE(ionj->rd_server_bytes, ioni->rd_server_bytes, itv) / fctr,
	       S_VALUE(ionj->wr_server_bytes, ioni->wr_server_bytes, itv) / fctr,
	       S_VALUE(ionj->rpc_sends, ioni->rpc_sends, itv),
	       S_VALUE(ionj->nfs_rops,  ioni->nfs_rops,  itv),
	       S_VALUE(ionj->nfs_wops,  ioni->nfs_wops,  itv));
}

/*
 ***************************************************************************
 * Print everything now (stats and uptime).
 *
 * IN:
 * @curr	Index in array for current sample statistics.
 * @rectime	Current date and time.
 ***************************************************************************
 */
void write_stats(int curr, struct tm *rectime)
{
	int i, fctr = 1;
	unsigned long long itv;
	struct io_hdr_stats *shi;
	struct io_nfs_stats *ioni, *ionj;

	/* Test stdout */
	TEST_STDOUT(STDOUT_FILENO);

	/* Print time stamp */
	if (DISPLAY_TIMESTAMP(flags)) {
		if (DISPLAY_ISO(flags)) {
			strftime(timestamp, sizeof(timestamp), "%FT%T%z", rectime);
		}
		else {
			strftime(timestamp, sizeof(timestamp), "%x %X", rectime);
		}
		printf("%s\n", timestamp);
#ifdef DEBUG
		if (DISPLAY_DEBUG(flags)) {
			fprintf(stderr, "%s\n", timestamp);
		}
#endif
	}

	/* Interval is multiplied by the number of processors */
	itv = get_interval(uptime[!curr], uptime[curr]);

	if (cpu_nr > 1) {
		/* On SMP machines, reduce itv to one processor (see note above) */
		itv = get_interval(uptime0[!curr], uptime0[curr]);
	}

	shi = st_hdr_ionfs;

	/* Display NFS stats header */
	write_nfs_stat_header(&fctr);

	for (i = 0; i < ionfs_nr; i++, shi++) {
		if (shi->used) {
			ioni = st_ionfs[curr]  + i;
			ionj = st_ionfs[!curr] + i;
#ifdef DEBUG
			if (DISPLAY_DEBUG(flags)) {
				/* Debug output */
				fprintf(stderr, "name=%s itv=%llu fctr=%d ioni{ rd_normal_bytes=%llu "
						"wr_normal_bytes=%llu rd_direct_bytes=%llu wr_direct_bytes=%llu rd_server_bytes=%llu "
						"wr_server_bytes=%llu rpc_sends=%lu nfs_rops=%lu nfs_wops=%lu }\n",
					shi->name, itv, fctr,
					ioni->rd_normal_bytes, ioni->wr_normal_bytes,
					ioni->rd_direct_bytes, ioni->wr_direct_bytes,
					ioni->rd_server_bytes, ioni->wr_server_bytes,
					ioni->rpc_sends,
					ioni->nfs_rops,        ioni->nfs_wops);
			}
#endif
			write_nfs_stat(curr, itv, fctr, shi, ioni, ionj);
		}
	}
	printf("\n");
}

/*
 ***************************************************************************
 * Main loop: Read stats from the relevant sources and display them.
 *
 * IN:
 * @count	Number of lines of stats to print.
 * @rectime	Current date and time.
 ***************************************************************************
 */
void rw_io_stat_loop(long int count, struct tm *rectime)
{
	int curr = 1;

	/* Don't buffer data if redirected to a pipe */
	setbuf(stdout, NULL);

	do {
		if (cpu_nr > 1) {
			/*
			 * Read system uptime (only for SMP machines).
			 * Init uptime0. So if /proc/uptime cannot fill it,
			 * this will be done by /proc/stat.
			 */
			uptime0[curr] = 0;
			read_uptime(&(uptime0[curr]));
		}
		/* Read NFS directories stats */
		read_nfs_stat(curr);

		/* Get time */
		get_localtime(rectime, 0);

		/* Print results */
		write_stats(curr, rectime);

		if (count > 0) {
			count--;
		}
		if (count) {
			curr ^= 1;
			pause();
		}
	}
	while (count);
}

/*
 ***************************************************************************
 * Main entry to the nfsiostat-sysstat program.
 ***************************************************************************
 */
int main(int argc, char **argv)
{
	int it = 0;
	int opt = 1;
	int i;
	long count = 1;
	struct utsname header;
	struct tm rectime;

#ifdef USE_NLS
	/* Init National Language Support */
	init_nls();
#endif

	/* Get HZ */
	get_HZ();

	/* Process args... */
	while (opt < argc) {

#ifdef DEBUG
		if (!strcmp(argv[opt], "--debuginfo")) {
			flags |= I_D_DEBUG;
			opt++;
		} else
#endif
		if (!strncmp(argv[opt], "-", 1)) {
			for (i = 1; *(argv[opt] + i); i++) {

				switch (*(argv[opt] + i)) {

				case 'h':
					/* Display an easy-to-read NFS report */
					flags |= I_D_HUMAN_READ;
					break;

				case 'k':
					if (DISPLAY_MEGABYTES(flags)) {
						usage(argv[0]);
					}
					/* Display stats in kB/s */
					flags |= I_D_KILOBYTES;
					break;

				case 'm':
					if (DISPLAY_KILOBYTES(flags)) {
						usage(argv[0]);
					}
					/* Display stats in MB/s */
					flags |= I_D_MEGABYTES;
					break;

				case 't':
					/* Display timestamp */
					flags |= I_D_TIMESTAMP;
					break;

				case 'V':
					/* Print version number and exit */
					print_version();
					break;

				default:
					usage(argv[0]);
				}
			}
			opt++;
		}

		else if (!it) {
			interval = atol(argv[opt++]);
			if (interval < 0) {
				usage(argv[0]);
			}
			count = -1;
			it = 1;
		}

		else if (it > 0) {
			count = atol(argv[opt++]);
			if ((count < 1) || !interval) {
				usage(argv[0]);
			}
			it = -1;
		}
		else {
			usage(argv[0]);
		}
	}

	if (!interval) {
		count = 1;
	}

	/* Select output unit (kB/s or blocks/s) */
	set_output_unit();

	/* Init structures according to machine architecture */
	io_sys_init();

	get_localtime(&rectime, 0);

	/* Get system name, release number and hostname */
	uname(&header);
	if (print_gal_header(&rectime, header.sysname, header.release,
			     header.nodename, header.machine, cpu_nr)) {
		flags |= I_D_ISO;
	}
	printf("\n");

	/* Set a handler for SIGALRM */
	memset(&alrm_act, 0, sizeof(alrm_act));
	alrm_act.sa_handler = (void *) alarm_handler;
	sigaction(SIGALRM, &alrm_act, NULL);
	alarm(interval);

	/* Main loop */
	rw_io_stat_loop(count, &rectime);

	/* Free structures */
	io_sys_free();

	return 0;
}
