AIX Tip of the Week

Using "iostat" to Determine IO Rate, Block Size

Audience: All

Date: April 22, 2002

Last weeks tip (http://www.aixtips.com/AIXtip/sizing_disk_for_io.htm ) showed a methodology for sizing the minimum number of disks to support a given transaction rate (IO per second)**.

The iostat command can be used to get the data for the sizing methodology. Here's how to calculate the statistics by disk:

I/O Rate = tps
Block size = kbps / tbps
%Read = ( KB_read )/( KB_read + KB_write )
%Write = ( KB_write )/( KB_read + KB_write )
Sample Interval = ( KB_read + KB_write )/ kbps

The statistics have to be summed over all disks. The attached "analyze_iostat" perl script does this summation. It can be run either as a pipe, or against a saved file. For example:

Pipe:               iostat 5 10 | analyze_iostat  
Saved file:     analyze_iostat 

The analyze_iostat output list overall statistics for each interval, as well as an average for the entire sample. The following is a sample of the output.

                      |--KB_Read---| |--KB_Write--|  AvBlkSz Interval
Count   kbps      TPS   % = R/(R+W)    % = W/(R+W)    (kb)   (sec)
-------------------------------------------------------------------------
1     4635.9   1159.0   26332 (100%)       0 (  0%)    4.0      6
2     4252.6   1062.8   24204 (100%)     121 (  0%)    4.0      6
3     4638.0   1159.5   26344 (100%)       0 (  0%)    4.0      6
4     4630.6   1157.6   26348 (100%)       0 (  0%)    4.0      6
5     4956.2    250.7    3492 ( 12%)   24560 ( 88%)   19.8      6
6     9770.6    179.9     172 (  0%)   55716 (100%)   54.3      6
7     5144.4    132.4    2032 (  7%)   27188 ( 93%)   38.9      6
8     2040.6     94.2    6196 ( 53%)    5456 ( 47%)   21.7      6
9     1914.2     92.1    5824 ( 53%)    5068 ( 47%)   20.8      6
10    2251.9     93.0    6676 ( 52%)    6160 ( 48%)   24.2      6
11    2289.8     88.2    6692 ( 52%)    6268 ( 48%)   26.0      6
-------------------------------------------------------------------------
Avg   4229.5    497.2   12210 ( 51%)   11867 ( 49%)   20.1      6
-------------------------------------------------------------------------

One caution. This script measures only physical I/O. If the disks are mirrored, there are two physical writes for each logical write. So, for example, if you are moving from mirrored to Raid 5, convert the KB_write and tps columns to logical writes.

(Observation: AIX 5.1.0.10 has a bug in the iostat output. To fix, update to current levels)


Perl script to analyze iostat data:

#!/usr/bin/perl
# Bruce Spencer IBM, 12apr02
# Purpose: estimate AIX block size, tps rate, and %read/%write 
$VERSION="1.0, April 12, 2002";

# If you want to list disks that exceed a threshold utilization
# set the next value to be greater than 0. 
# Turn off by setting to 0
$THRESHOLD_DISK_UTIL=40;

# Help screen  
if ( $ARGV[0] eq "-h") {
	$PRGM=$0;
	system("tput clear");
	print("Program name:  $PRGM, Version $VERSION \n\n");
	print("Purpose:  Analyze iostat output to estimate I/O block size, transactions/sec, read/write content.\n\n");
	print("Usage:  $PRGM [iostat file]\n\n\n");
	print("Example 1: Input from file.\n");
	print("\n\tiostat 10 30 > iostat.out\n");
	print("\t$PRGM iostat.out \n\n");
	print("Example 2: Input from stdin.\n");
	print("\n\tiostat 10 30 \| $PRGM\n\n");
	print("Note: Be sure there is more than one iostat interval in the sample.\n");
	print("This program skips the first interval (historic data).\n\n");
	exit(0);
} # end if


# Determine if input is stdin or file
if (@ARGV == 0) { 
	#No file specified, assume stdin
	$INFILE="<&0";
	$|=1; # Don't buffer input
} else{
	$INFILE=$ARGV[0];
} # end if

# Open file or stdin
unless (open(INPUT, $INFILE)) { die ("File open error\n");} 

# Debug
# unless (open(INPUT, "iostat.out")) { die ("File open error\n");} 


# Read the iostat data for each interval
# 1. Data for each interval starts after line "Disks:  %tm_act....."
# 2. Read interval data. Output interval stats. Accumulate overall stats.
# 3. Interval data ends with "^cd0" or eof.


# Print header for output
$~="HEADER";
write;
$~="LINE";
write;

# Switch to column format for output
$~="COLUMNS";


# Skip first iostat interval because it is historic data
&next_interval;

# read iostat data for each interval...the $read_next subroutine detects eof
while ( 1 ) {   

	# Skip to first line with data in next interval
	&next_interval;

	# Zero counters for this interval
	&zerocounters;

	# Start reading the data, the &read_next checks for eof
	while ( &read_next ) {

		# Check for end of interval (CDROM or empty line)
		if ($input =~ /^cd/ || $input eq "" ) {last;}

		# Parse the line for I/O data
		@cols =	split(/\s+/,$input);
		$count++;
		$sum_kbps+=$cols[2];
		$sum_tps+=$cols[3];
		$sum_read+=$cols[4];
		$sum_write+=$cols[5];
		if ( $cols[3] > 0 ) {
			$sum_blocksize+=$cols[2]/$cols[3];
			$blocksize_count++;
			} #end if
		$disk_util{$cols[0]}+= $cols[1]; 

	} # end while

	# Summarize the I/O data for this interval
	&summarize;

}# end while

close(INPUT);



##############################################################
#  Subroutines
##############################################################

# Calculate grand totals
sub grandtotals() {

	# Calculate the overall statistics 
	# Reuse interval variables so we can use the write function
 
	# Set print format
	$~="COLUMNS";

	if ($interval >0) {

		$sum_kbps = $kbps_overall/$interval;
		$sum_tps  = $tps_overall/$interval;
		$sum_read = $read_overall/$interval;
		$sum_write= $write_overall/$interval;
		$sum      = $read_overall + $write_overall;


		if ( $sum >0 ) {
			$pct_read=100*($read_overall)/$sum;
			$pct_write=100 - $pct_read;
		} else {
			$pct_read="";
			$pct_write="";
		}


		if ($block_overall_cnt >0 ){
			$blocksize=$block_overall/$block_overall_cnt;
		} else {
			$blocksize="";
		} # end if


#		if ( $tps_overall > 0 ) {
#			$time_int= $sum/$tps_overall;
#		} else {
#			$time_int="";
#		} # end if

		# Save the interval count in "count"
		$count=$interval;
		$interval="Avg";
		
		# Print horizontal line
		$~="LINE";
		write;		

		# Print the overall averages
		$~="COLUMNS";
		write;		

	} #end if

	# Print out disk that exceeded threshold
	if ($count > 0 && $THRESHOLD_DISK_UTIL > 0 ) {

		# Print horizontal line
		$~="LINE";
		write;

		# Print heading
		$~="THRESHOLD_HEADER";
		write;

		# Set print format for threshold
		$~="THRESHOLD";

		foreach $disk ( keys %disk_util ) {
			$disk_util{$disk}/=$count;
			if ( $disk_util{$disk} > $THRESHOLD_DISK_UTIL ) {
				write;
			} # end if
		} # end foreach
	} # end if

} # end grandtotals




# Summarizes the interval statistics
sub summarize() {

	if ( $count < 1  ) {
		print ("Input error: Disk count equals zero. Exiting\n");
		exit;
	} # end if

	# Interval statistics

	$sum= $sum_read + $sum_write;
	if ( $sum > 0 ) {
		$pct_read= 100*($sum_read)/$sum;
		$pct_write=100- $pct_read;
	} else {
		$pct_read=" ";
		$pct_write=" ";
	}

	# Sample Interval (seconds)
	if ($sum_kbps > 0) {
		$time_int= $sum/$sum_kbps;
	} else {
		$time_int="";
	}

	if ($blocksize_count == 0) {
		$blocksize=" ";
	} else {
		$blocksize=$sum_blocksize/$blocksize_count;
	} # end if


	# Sum statistics for global sample
	$interval++;
	$kbps_overall+=$sum_kbps;
	$tps_overall+=$sum_tps;
	$read_overall+=$sum_read;
	$write_overall+=$sum_write;

	if ($blocksize_count >0) {
		$block_overall += $blocksize;
		$block_overall_cnt++;
	} # end if

	# Print out interval summary
	write;

} #end summarize


# Zero out interval counters
sub zerocounters() {
	$count=0;
	$sum_tps=0;
	$sum_read=0;
	$sum_write=0;
	$sum_blocksize=0;
	$blocksize_count=0;
	$sum_kbps=0;
} # end zerocounters


# Increment line to next iostat interval
sub next_interval {


	#Increment to beginning of next iostat interval
	do {
		&read_next;
	} until ($input =~ /^Disks/);


} # end next_interval
 

# Read next line 
sub read_next {

	$input=<INPUT> ;

	# check for end of file
	if ( eof ) {
		&summarize;
		&grandtotals;
		exit;
	} #end if

	chop($input);

} # end read_next



##############################################################
#  Print formats
##############################################################

format HEADER =
                      |--KB_Read---| |--KB_Write--|  AvBlkSz Est_Interval
Count   kbps      TPS   % = R/(R+W)    % = W/(R+W)    (kb)   (sec)
.

format COLUMNS =
@<<< @####.#  @####.# @###### (@##%) @###### (@##%) @###.#   @###.#
$interval, $sum_kbps, $sum_tps, $sum_read, $pct_read, $sum_write, $pct_write, $blocksize, $time_int
. 

format LINE =
-------------------------------------------------------------------------
.

format THRESHOLD_HEADER =

The following disks exceed the threshold @###.# % utilization.
$THRESHOLD_DISK_UTIL 

.


format THRESHOLD =
@<<<<<<<<<<   @###.#
$disk, $disk_util{$disk} 
.

Bruce Spencer,
baspence@us.ibm.com