#! /bin/bash # # smart_spindown rev. 1 # # Copyright (C) 2003 by Bart Samwel # # You may do with this file (and parts thereof) whatever you want, as long # as my copyright notice is retained. # # # How it works: This program monitors the read activity on a disk. If there # is no read activity for a while, the disk is spun down. The time without # read activity that is required is dynamic, using a backoff factor. When # the recent spun-down periods are relatively short, this means that the # machine might be busy with something, so the script tries to wait for # longer periods without activity before spinning down again. When spun-down # periods are long, the backoff factor is decreased, and the disk is spun # down after shorter periods without read activity. # # This script REQUIRES that laptop_mode is enabled on your kernel. This is # because it assumes that after a couple of seconds without read activity, # all dirty blocks will be flushed. If this is not done, the disc will # spin up at random times # # Configuration # # Output levels. Level 2 is verbose, level 1 is normal output. # Enable all levels you would like to see. OUTLEVEL1=true OUTLEVEL2=false # Disk to monitor. DISK=hdb # Device name for the disk. DEVNAME=/dev/$DISK # Stats file: the file used to monitor the disk's read activity. # The first entry in this stats file must represent the read activity. STATSFILE=/sys/block/$DISK/stat # Multiplication factor for the backoff after a spinup, in percentages. # Default is 300 = factor 3. BACKOFF_INCREASE_PCT=300 # Multiplication factor for the backoff at every poll that shows that # the disk is spun down. This determines how fast the backoff value # decreases. BACKOFF_DECREASE_PCT=96 # The base "no reads" wait time (in seconds). This is multiplied by # the backoff factor to determine the real "no reads" wait time. WAITTIME=20 # The maximum "no reads" wait time (in seconds). # This also limits the backoff factor: the backoff factor cannot increase # above a value that makes the "no reads" wait time larger than MAXWAIT. # Default is 120 seconds. MAXWAIT=120 # Time (in seconds) between polls to see if the disk is active again. # Default is 10 seconds. POLLTIME=10 # Enable this if you don't use laptop_mode. This will make the script # sync before spinning down the disc. To make this work, you must # ensure that: # 1. /proc/sys/vm/dirty_expire_centisecs is set to a high value. You can # use 60000 for 10 minutes. # 2. /proc/sys/vm/dirty_writeback_centisecs is set to the same value. # 3. Your ext3 filesystems are mounted with "commit=n", where n is the # number of seconds between commit. Use 600 for 10 minutes. NO_LAPTOP_MODE=false # # Let's go! # # Number of poll times that the disc was found to be spun down. POLLSSPUNDOWN=0 # Number of spindowns performed SPINDOWNS=0 # Number of times (*100) the WAITTIME of no-reads required before spindown BACKOFF_FACTOR=100 # Stats: Total time the disk has been up. UPTIME=0 # Total duration of last spun-down period. LASTDOWNTIME=0 # Total duration of the last spun-up period. LASTUPTIME=0 # Duration of the last poll. Always equal to POLLTIME except the first # time around. LASTPOLLTIME=0 # Make sure the stuff we use is in the cache. I've seen it happen # that the script spun the disk down, and then "sleep" wasn't in # the cache and the disk spun right up again. :) true false sleep 1 $OUTLEVEL1 && echo Monitoring spindown opportunities for disk $DISK. if ($OUTLEVEL1) ; then hdparm -C $DEVNAME |grep active >/dev/null if [ "$?" == "0" ] ; then echo Drive is currently spun up. ; else echo Drive is currently spun down. ; fi ; fi while [[ /sbin/true ]]; do hdparm -C $DEVNAME |grep active >/dev/null if [ "$?" == "0" ] ; then THISWAIT=$(($WAITTIME*$BACKOFF_FACTOR/100)) ; if [[ $THISWAIT -gt $MAXWAIT ]] ; then THISWAIT=$MAXWAIT ; fi ; # Increase the backoff irrespective of whether we failed # or not. The backoff should drop again by the lack of # spinups afterwards. BACKOFF_FACTOR=$(($BACKOFF_FACTOR*$BACKOFF_INCREASE_PCT/100)) ; if [[ $(($BACKOFF_FACTOR*$WAITTIME/100)) -gt $MAXWAIT ]] ; then BACKOFF_FACTOR=$(($MAXWAIT*100/$WAITTIME)) ; fi ; UPTIME=$(($UPTIME+$LASTPOLLTIME)) ; LASTUPTIME=$(($LASTUPTIME+$LASTPOLLTIME)) ; if [ "$LASTDOWNTIME" != "0" ] ; then $OUTLEVEL1 && echo Drive spun up after $LASTDOWNTIME seconds. ; fi PREVIOUS_READS=-1 ; NUM_EQUALS=0 ; $OUTLEVEL2 && echo Waiting for $THISWAIT seconds of read inactivity... ; PREVIOUS_READS=`cat $STATSFILE |awk '{ print $1; }'` ; while [[ $(($NUM_EQUALS*5)) -lt $THISWAIT ]]; do sleep 5 ; UPTIME=$(($UPTIME+5)) ; LASTUPTIME=$(($LASTUPTIME+5)) ; NEXT_READS=`cat $STATSFILE |awk '{ print $1; }'` ; if [[ $PREVIOUS_READS -ne $NEXT_READS ]] ; then NUM_EQUALS=0 ; PREVIOUS_READS=$NEXT_READS $OUTLEVEL2 && echo Restarting... ; else NUM_EQUALS=$(($NUM_EQUALS+1)) ; $OUTLEVEL2 && echo Seconds of quiet: $(($NUM_EQUALS*5)) ; fi done # We've just had $THISWAIT seconds of read inactivity. Writes can be # cached, reads always spin up the disk; the inactivity indicates # that we're ready to go to sleep. Laptop mode will have synced all # writes for us after the last read, so we don't have to explicitly # sync. if ( $NO_LAPTOP_MODE ) ; then sync ; fi ; hdparm -q -y $DEVNAME ; SPINDOWNS=$(($SPINDOWNS+1)) ; $OUTLEVEL1 && echo Drive spun down after $LASTUPTIME seconds \(with $THISWAIT seconds of inactivity\). ; LASTUPTIME=0 ; LASTDOWNTIME=0 ; else POLLSSPUNDOWN=$(($POLLSSPUNDOWN+1)) ; if [[ $SPINDOWNS -eq 0 ]] ; then SPINDOWNS=1 ; fi LASTDOWNTIME=$(($LASTDOWNTIME+$LASTPOLLTIME)) ; BACKOFF_FACTOR=$(($BACKOFF_FACTOR*$BACKOFF_DECREASE_PCT/100)) ; if [ $BACKOFF_FACTOR -lt 100 ] ; then BACKOFF_FACTOR=100 ; fi fi ; if ( $OUTLEVEL2 ) ; then echo -n spindowns: $SPINDOWNS, time up/down: $UPTIME/$(($POLLSSPUNDOWN*$POLLTIME)), backoff $BACKOFF_FACTOR, down for $LASTDOWNTIME \(avg $(($POLLSSPUNDOWN*$POLLTIME/$SPINDOWNS))\). ; fi ; sleep $POLLTIME ; LASTPOLLTIME=$POLLTIME ; done