#!/usr/bin/perl # This software code is made available "AS IS" without warranties of any # kind. You may copy, display, modify and redistribute the software # code either by itself or as incorporated into your code; provided that # you do not remove any proprietary notices. Your use of this software # code is at your own risk and you waive any claim against the author # with respect to your use of this software code. # (c) 2006 Gregory Bell # # usage: # ionice # What is it: # A poor man's attempt at the real "ionice" which isn't supported # until 2.6.13 (the boxes I use are based on RHEL 4) # The theory is to run processes which are known to be IO-intensive and # low-priority (such as a daily backup) under 'ionice' to make them # good neighbors to the rest of the system in terms of IO usage. # It tries to prevent IO starvation of other processes, which may be # more important to have fast response time. # The name of this utility (ionice) is unfortunately a bit of a misnomer. # There's no way to flag a process as having a different "io priority" # than others, which would be the ideal solution. # Instead, this is more of a "blunt instrument" approach to the problem. # Theory of operation: # Launch a coprocess and when system iowait gets too high, # suspend the coprocess for a while. # When the iowait gets back to an acceptable level, re-activate copro. # Configurable things: $MAX_ALLOWED_IOWAIT_PERCENT = 50; # of whole system $MIN_BACKOFF_SECONDS = 0.5; # how often do we poll the IO $BACKOFF_MULTIPLIER = 1.5; # higher value = greater back-off use POSIX ":sys_wait_h"; use Time::HiRes qw(sleep); #initialize the bins (values accumulate over time) my ($lastio, $lasttotal) = ReadProcStat(); sub ReadProcStat { open(PROCSTAT, "; close(PROCSTAT); my @words = split(/\s+/); my $alltimes = 0; my $i = 0; for($i = 1; $i < 8; $i++) { $alltimes = $alltimes + $words[$i]; } my $iowait = $words[5] + 0; return ($iowait, $alltimes); } # coprocess GO! $copid = fork(); if(0 == $copid) { # this is the child process # become the copro exec(@ARGV); } my $backoff = $MIN_BACKOFF_SECONDS; my $suspended = 0; while(0 == waitpid($copid, &WNOHANG)) { my ($currentio, $currenttotal) = ReadProcStat(); my $deltaio = $currentio - $lastio; my $deltatotal = $currenttotal - $lasttotal; ($lastio, $lasttotal) = ($currentio, $currenttotal); # don't divide by zero if($deltatotal == 0) { $deltatotal = 1; } my $iowait = ($deltaio / $deltatotal) * 100; if($MAX_ALLOWED_IOWAIT_PERCENT < $iowait) { if(0 == $suspended) { $suspended = 1; kill('STOP', $copid); } else { # still bad; slow down checking $backoff = $backoff * $BACKOFF_MULTIPLIER; } } elsif($MAX_ALLOWED_IOWAIT_PERCENT >= $iowait and 1 == $suspended) { $suspended = 0; $backoff = $MIN_BACKOFF_SECONDS; kill('CONT', $copid); } sleep($backoff); }