#!/usr/bin/perl # iorate.pl 1.8 2004-01-20 18:29:07-05 root Exp # show i/o rates from the /proc/partitions and /proc/stat files sub read_init; sub get_partition_names; sub Usage; sub dbug; use Getopt::Std; getopts("zpPs:m:Hd") || Usage; # sanity check Usage if $opt_P && $opt_p; $SleepTime = ( $opt_s ? $opt_s : 60 ); $MaxSamples = ( $opt_m ? $opt_m : 0 ); $Debug = 0 + $opt_d; if ($opt_H) { $hdr = sprintf("%-5s %10s %10s %10s %10s %10s", "", "tot-io/s", "rio/s", "rMB/s", "wio/s", "wMB/s"); } # get the initial values $PartFull = $opt_P; dbug("main", "before call get_part_names"); get_partition_names; dbug("main", "after names, start init"); read_init(\%baseval); $Nkeys = keys %baseval; dbug("main", "after 1st init, hda:rio is $baseval{hda}{rio}, keys: $Nkeys"); # save the base, enter a loop to calculate differences for $v (keys %baseval) { $current{$v} = $baseval{$v} } # main output loop for (;;) { my ($new, $did_io); my ($NeedHdr) = 1; sleep $SleepTime; read_init(\%new); for $v (sort keys %new) { local ($OLD, $NEW); *OLD = $current{$v}; *NEW = $new{$v}; $did_io = ($NEW{opps}>$OLD{opps}); dbug("loop", "info: $v, name: $partname{$v}"); if ((!$opt_z || $did_io) && ( $NEW{minor} % 16 ? ($opt_p || $opt_P) && defined $partname{$v} : !$opt_P ) ) { if ($NeedHdr) { # about to print a value, no header yet $NeedHdr = 0; print ( $opt_H ? "$hdr\n" : "\n" ); } printf("%-5s %10.1f %10.1f %10.2f %10.1f %10.2f %s\n", $v, ($NEW{opps} - $OLD{opps})/$SleepTime, ($NEW{rio} - $OLD{rio})/$SleepTime, ($NEW{rsect} - $OLD{rsect})/($SleepTime*2*1024), ($NEW{wio} - $OLD{wio})/$SleepTime, ($NEW{wsect} - $OLD{wsect})/($SleepTime*2*1024), $partname{$v} ); } } # copy the values and delete the fields for $v (keys %new) { $current{$v} = $new{$v}; delete $new{$v}; } # end logic here last if $MaxSamples && --$MaxSamples == 0; } exit 0; sub read_init { local (*partinfo) = @_; my ($major, $minor, $size, $name, $rio, $rmerg, $rsect, $ruse, $wio, $wmerg, $wsect, $wuse, $running, $use, $avQ ); my ($device, $lun, $wptr, $isdisk, $tmp); my ($RHproc, $v26) = (0.0); # try to open diskstats first, then partitions if (open(PT, "/proc/diskstats")) { $v26 = 1; } elsif (open(PT, "/proc/partitions")) { # check for Redhat or similar with io stats $_ = ; @headers = split; die "No io stats in /proc/partitions" if $#headers < 4; $RHproc = 1; } else { die "No proc f/s? ($!)"; } while () { if ($v26) { ($major, $minor, $name, @tmp) = split; dbug("v26 split", sprintf("%s %d %d %d", $name, $major, $minor, $#tmp)); if ($#tmp == 3) { # partition ($rio, $rsect, $wio, $wsect) = @tmp; dbug("v26_part", "$name, rio: $rio, wio: $wio"); } else { ($rio, $rmerg, $rsect, $ruse, $wio, $wmerg, $wsect, $wuse, $running, $use, $avQ ) = @tmp; } } elsif ($RHproc) { ($major, $minor, $size, $name, $rio, $rmerg, $rsect, $ruse, $wio, $wmerg, $wsect, $wuse, $running, $use, $avQ ) = split; } else { die "stat type not defined"; } dbug("loop-ar", "name=$name, rio=$rio"); $device = int($minor / 16); $lun = $minor % 16; $isdisk = $name =~ m/[sh]d[a-z]+$/; # check for incomplete partition info # next if $ispart{$name} && !($PartFull || ($opt_p && ($rio || $wio))); # save a pointer to the base data if (defined $partname{$name}) { $partinfo{$name} = {}; *wptr = $partinfo{$name}; $wptr{major} = $major; $wptr{minor} = $minor; $wptr{rio} = $rio; $wptr{rsect} = $rsect; $wptr{wio} = $wio; $wptr{wsect} = $wsect; $wptr{opps} = $rio + $wio; } } close PT; dbug("exit_init", sprintf("keys: %d", keys %partinfo)); } # get partition names - mount point or "swap" sub get_partition_names { my ($pname, $size1, $size2, $size3, $used, $mpoint, @tmp); # do all the known devices, just to validate open(PR, "/proc/partitions") || die "no partition table (no proc fs?)"; while () { @tmp = split; if ($tmp[0] > 0 && $tmp[2] > 1) { $pname = $tmp[3]; $partname{$pname} = ( $pname =~ m/[hs]d[a-z]+$/ ? "" : "unmounted" ); $ispart{$pname} = ($tmp[1] > 0); } } close PT; # mounts first open(DF, "df|") || warn "Can't run df, nameless partitions"; while () { ($pname, $size1, $size2, $size3, $used, $mpoint) = split; if ($pname =~ m#/dev/(\S+)#) { $partname{$1} = $mpoint; } } close DF; # now identify the swap partitions if (open(SW, "/proc/swaps")) { while () { $partname{$1} = "swap" if m#/dev/(\S+)#; } close SW; } } # provide usage info on user request sub Usage { my ($rev) = '1.8'; print STDERR "diorate - $rev\n" . "\n" . "Usage:\n" . " diorate [ options ]\n" . "\n" . "Displays the total, read, and write activity for each drive\n" . "on a repeating basis. This may be modified by options to show\n" . "activity per partition instead of or in addition to the drive\n" . "display.\n" . "\n" . "Options:\n" . " -H display a header with each data set\n" . " -P display per-partition activity instead of per-drive\n" . " -p display per-partition activity in addition to pre-drive\n" . " -z do not display drives or partitions with no activity\n" . " -sNN repeat every NN sec (def: 60)\n" . " -mNN display max NN datasets\n" . "\n" . "Note: -p and -P are mutually exclusive!\n" . "\n"; exit 1; } # print debugging info sub dbug { my ($caller, $text) = @_; printf STDERR "%-10s %s\n", $caller, $text if $Debug; } # 11 fields # # 1 - reads completed # 2 - reads merged # 3 - sectors read # 4 - ms spent reading # 5 - writes completed # 6 - writes merged # 7 - sectors written # 8 - ms spent writing # 9 - io in progress # 10 - ms spent doing io (clear is 9 zero) # 11 - ms spent doing all io (?) # # 2.6 partition info # # 1 - reads issues # 2 - sectors read # 3 - writes issues # 4 - sectors written