/* iostat.c	-- Greg Franks Wed Mar 10 1999
 *
 * Linux /proc version of the IOStat program.
 *
 * $Log: iostat.c,v $
 * Revision 1.1  1999/03/25 19:32:46  greg
 * Initial revision
 *
 */

#ifndef lint
static char *rcsid = "$Header: /a/antares/export/home/greg/src/iostat/RCS/iostat.c,v 1.1 1999/03/25 19:32:46 greg Exp greg $";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/param.h>
#include <linux/major.h>

static char * opts	= "cdDpPx";
static char * my_name;

int print_cpu		= 0;
int print_disk_extended = 0;
int print_disk_util	= 0;
int print_partition	= 0;
int print_device	= 1;

#define NAME_SIZE	32

typedef struct {
	unsigned rd_ios;		/* Read io operations.		*/
	unsigned rd_merges;		/* Operations merged		*/
	unsigned rd_sectors;		/* Sectors read			*/
	unsigned rd_ticks;		/* Time in queue+service	*/
	unsigned wr_ios;
	unsigned wr_merges;
	unsigned wr_sectors;
	unsigned wr_ticks;
	unsigned ticks;			/* Time of requests in queue	*/
	unsigned aveq;			/* Average queue length		*/
} blkio_info_t;

typedef struct {
	unsigned major;			/* Device major number 		*/
	unsigned minor;			/* Device minor number 		*/
	unsigned sizes;			/* ? */
	char name[NAME_SIZE];
} partition_info_t;

typedef struct {
	unsigned user;
	unsigned syst;
	unsigned idle;
} cpu_info_t;


#define MAX_PARTITIONS 16
partition_info_t partition[MAX_PARTITIONS];
blkio_info_t new_blkio[MAX_PARTITIONS], old_blkio[MAX_PARTITIONS];
unsigned n_partitions = 0;

cpu_info_t new_cpu, old_cpu;

FILE * blkio_fptr;		/* /proc/partition 	*/
FILE * cpu_fptr;		/* /proc/stat.		*/

static void usage();
static void initialize( char * match_list[], int count );
static void process( int lineno );
static void header( int lineno );
static void get_kernel_stats();

static void print_disk_stats();
static void print_partition_stats();
static void print_cpu_stats();
static int print_partition_record( int i );

int
main( int argc, char **argv )
{
	int i;
	int c;
	int interval = 5;		/* Time twixt samples 	*/
	int count    = -1;		/* Total samples	*/
	int n_dev    = 0;		/* Number of devices	*/
	
	my_name = argv[0];
	
	blkio_fptr = fopen( "/proc/partitions", "r" );
	if ( !blkio_fptr ) {
		fprintf( stderr, "%s: ", my_name );
		perror( "Cannot open /proc/stat" );
		exit( 2 );
	}
	cpu_fptr = fopen( "/proc/stat", "r" );
	if ( !cpu_fptr ) {
		fprintf( stderr, "%s: ", my_name );
		perror( "Cannot open /proc/stat" );
		exit( 2 );
	}

	while (( c = getopt( argc, argv, opts )) != EOF) {
		switch( c ) {

		case 'c':
			print_cpu = 1;
			break;

		case 'd':
			print_disk_util = 0;
			break;

		case 'D':
			print_disk_util = 1;
			break;

		case 'p':
			print_partition = 1;
			break;

		case 'P':
			print_device = 0;
			break;

		case 'x':
			print_disk_extended = 1;
			break;
			
		default:
			usage();
		}
	}

	/* No options.  Set defaults */
	
	if ( optind == 1 ) {
		print_cpu = 1;
	}
	
	/* List of disks/devices [delay [count]].  */

	for ( n_dev = 0; optind + n_dev < argc && !isdigit( argv[optind+n_dev][0] ) ; ++n_dev );

	initialize( &argv[optind], n_dev );

	optind += n_dev;
	
	/* Figure out [delay [count]].  Default is one display only */
	
	switch ( argc - optind ) {
	case 2:
		count = atoi( argv[optind+1] );
	case 1:
		interval = atoi( argv[optind] );
		break;
		
	case 0:
		count = 0;
		break;
		
	default:
		usage();
	}

	/* Let 'er rip! */
	
	for ( i = 0; ; i = (i + 1) % 20 ) {
		process( i );
		
		if ( count > 0 ) {
			count -= 1;
		}
		if ( count == 0 ) break;

		sleep( interval );
	}
}


/*
 * Get partition names.  Check against match list.
 */

static void initialize( char * match_list[], int n_dev )
{
	char buf[256];
	
	/* Get device names */

	partition_info_t curr;
	

	while ( fgets( buf, sizeof(buf), blkio_fptr ) ) {
		int ticks = 0;
		
		if ( sscanf( buf, "%d %d %d %31s %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %*d",
			     &curr.major, &curr.minor, &curr.sizes, curr.name, &ticks ) == 5 ) {
			int i;
			
			for ( i = 0; 
			      i < n_partitions && ( partition[i].major != curr.major
						    || partition[i].minor != curr.minor );
			      ++i );

			if ( i == n_partitions && i < MAX_PARTITIONS ) {
				if ( n_dev ) {
					int j;

					for ( j = 0; j < n_dev && match_list[j]; ++j ) {
						if ( strcmp( curr.name, match_list[j] ) == 0 ) {
							partition[i] = curr;
							n_partitions = i + 1;
						}
					}
					
				} else if ( print_partition_record( i ) && ticks ) {
					partition[i] = curr;
					n_partitions = i + 1;
				}
			}
		}
	}
}



/*
 * Print usage info then exit.
 */

static void usage()
{
	fprintf( stderr, "%s ...\n", my_name );
}


/*
 * Main loop...
 */

static void process( int lineno )
{
	int i;
	
	get_kernel_stats();
	header( lineno );

	print_partition_stats();
	if ( print_cpu && !print_disk_extended ) {
		print_cpu_stats();
	}

	/* Save old counts */

	for ( i = 0; i < n_partitions; ++i ) {
		old_blkio[i] = new_blkio[i];
	}
	old_cpu  = new_cpu;
	
	printf( "\n" );
}



/*
 * Print a header if lineno == 0.
 */

static void header( int lineno )
{
	int i;
	
	if ( lineno != 0 ) return;

	/* ---------- Line 1 ---------- */
	
	if ( print_disk_extended ) {
		printf( "%74s  ", "extended device statistics" );
	} else for ( i = 0; i < n_partitions; ++i ) {
		printf( "%12.12s  ", partition[i].name );
	}

	if ( print_cpu ) {
		printf( "        cpu" );
	}
	printf( "\n" );

	/* ---------- Line 2 ---------- */
	
	if ( print_disk_extended ) {
		printf( "name     mgr/s mgw/s  r/s  w/s   kr/s   kw/s avesz  aveq  wait  svct  util  " );
	} else for ( i = 0; i < n_partitions; ++i ) {
		if ( print_disk_util ) {
			printf( "r/s w/s util  " );
		} else {
			printf( "k/s t/s serv  " );
		}
	}
	if ( print_cpu ) {
		printf( " us  sy  id" );
	}
	printf( "\n" );
}



/*
 * Get data from kernel.  Easy way /proc/stat.
 */

static void get_kernel_stats()
{
	char buf[256];
	
	rewind( blkio_fptr );
	while ( fgets( buf, sizeof(buf), blkio_fptr ) ) {
		partition_info_t curr;
		blkio_info_t blkio;
		
		if ( sscanf( buf, "%d %d %*d %*31s %d %d %d %d %d %d %d %d %*d %d %d",
			     &curr.major, &curr.minor,
			     &blkio.rd_ios, &blkio.rd_merges, &blkio.rd_sectors, &blkio.rd_ticks, 
			     &blkio.wr_ios, &blkio.wr_merges, &blkio.wr_sectors, &blkio.wr_ticks,
			     &blkio.ticks, &blkio.aveq ) == 12 ) {
			int i;

			/* Locate partition in data table */
				
			for ( i = 0; i < n_partitions; ++i ) {
				if ( partition[i].major == curr.major
				     && partition[i].minor == curr.minor ) {
					new_blkio[i] = blkio;
					break;
				}
			}
		}
	}

	rewind( cpu_fptr );
	while ( fgets( buf, sizeof(buf), cpu_fptr ) ) {
		if ( strncmp( buf, "cpu ", 4 ) == 0 ) {
			int user, nice;
			
			sscanf( buf, "cpu %d %d %d %d", &user, &nice, &new_cpu.syst, &new_cpu.idle );
			new_cpu.user = user + nice;
		} 
	}
}


/*
 * Print out statistics.
 * extended form is:
 *   read merges,
 *   write merges,
 *   read io requests,
 *   write io requests,
 *   kilobytes read,
 *   kilobytes written,
 *   average queue length,
 *   average waiting time (queue + service)
 *   average service time at disk (derive from tput and util.)
 *   average disk utilization.
 *
 * Linux uses 1K blocks (except for CD-roms which are 2K, but I don't how to
 * figure out whether the device is a CD rom or not.
 */

static void print_partition_stats()
{
	int i;
	double delta = (double)(new_cpu.user + new_cpu.syst + new_cpu.idle)
		- (old_cpu.user + old_cpu.syst + old_cpu.idle);


	for ( i = 0; i < n_partitions; ++i ) {
		blkio_info_t blkio;
		double tput;			/* Throughput to disk	*/
		double util;			/* Utilization at disk	*/
		double avwait;	 		/* Average wait 	*/
		double svtime;			/* Disk service time.	*/
		double avsize;			/* Average request size	*/
		double n_ios;
		
		blkio.rd_ios =	   new_blkio[i].rd_ios     - old_blkio[i].rd_ios;
		blkio.rd_merges =  new_blkio[i].rd_merges  - old_blkio[i].rd_merges;
		blkio.rd_sectors = new_blkio[i].rd_sectors - old_blkio[i].rd_sectors;
		blkio.rd_ticks =   new_blkio[i].rd_ticks   - old_blkio[i].rd_ticks;
		blkio.wr_ios =	   new_blkio[i].wr_ios	   - old_blkio[i].wr_ios;
 		blkio.wr_merges =  new_blkio[i].wr_merges  - old_blkio[i].wr_merges; 
		blkio.wr_sectors = new_blkio[i].wr_sectors - old_blkio[i].wr_sectors;
		blkio.wr_ticks =   new_blkio[i].wr_ticks   - old_blkio[i].wr_ticks;
		blkio.ticks =	   new_blkio[i].ticks	   - old_blkio[i].ticks;
		blkio.aveq =	   new_blkio[i].aveq	   - old_blkio[i].aveq;
		
		n_ios  = blkio.rd_ios + blkio.wr_ios;
		tput   = n_ios * HZ / delta;
		util   = blkio.ticks / delta;		/* In mS */
		svtime = tput ? util / tput : 0.0;
		avwait = n_ios ? (blkio.rd_ticks + blkio.wr_ticks) / n_ios * 1000.0 / HZ : 0.0;
		avsize = n_ios ? (double)(blkio.rd_sectors + blkio.wr_sectors) / n_ios : 0.0;
		
		if ( print_disk_extended ) {
			/*        name  rm/s  wm/s  r/s   w/s   kr/s  kw/s  avesz aveq  await svtim util */
			printf( "%-8.8s %5.1f %5.1f %4.1f %4.1f %6.1f %6.1f %5.1f %5.1f %5.1f %5.1f %5.1f  ",
				partition[i].name,
				(double)blkio.rd_merges * HZ / delta,
				(double)blkio.wr_merges * HZ / delta,
				(double)blkio.rd_ios * HZ / delta,
				(double)blkio.wr_ios * HZ / delta,
				(double)blkio.rd_sectors * HZ / delta,
				(double)blkio.wr_sectors * HZ / delta,
				avsize,				/* Average request size */
				(double)blkio.aveq / delta,	/* Queue length	*/
				avwait,				/* Waiting for svc. */
				svtime * 1000.0,		/* Service time in mS. */
				util * 100.0 );			/* Utilization in % */
			if ( i == 0 && print_cpu ) {
				print_cpu_stats();
			}
			printf( "\n" );
		} else if ( print_disk_util ) {
			printf( "%3.0f %3.0f %4.1f  ",
				(double)blkio.rd_ios * HZ / delta,
				(double)blkio.wr_ios * HZ / delta,
				util * 100.0 );			/* Utilization in % */
		} else {
			printf( "%3.0f %3.0f %4.1f  ", 
				(double)(blkio.rd_sectors + blkio.wr_sectors) * HZ / delta,
				tput,
				svtime * 1000.0 );		/* Service time in mS. */
		}
	}
}


static void print_cpu_stats()
{
	cpu_info_t cpu;
	double total;

	cpu.user = new_cpu.user - old_cpu.user;
	cpu.syst = new_cpu.syst - old_cpu.syst;
	cpu.idle = new_cpu.idle - old_cpu.idle;
	total    = (cpu.user + cpu.syst + cpu.idle) / 100.0;	/* Want % */
	
	printf( "%3.0f %3.0f %3.0f", cpu.user / total, cpu.syst / total, cpu.idle / total );
}



/*
 * Check if the index refers to a disk or a partition.
 */

static int print_partition_record( int i )
{
	if ( partition[i].major == IDE0_MAJOR || partition[i].major == IDE1_MAJOR ) {
		return ( (partition[i].minor & 0x3F) == 0 && print_device )
			|| ( (partition[i].minor & 0x3F) != 0 && print_partition );
	} else if ( partition[i].major == SCSI_DISK_MAJOR ) {
		return ( (partition[i].minor & 0xF) == 0 && print_device )
			|| ( (partition[i].minor & 0xF) != 0 && print_partition );
	} else {
		return 1;
	}
}
