/* 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 #include #include #include #include #include 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; } }