Smart spindown

i.e. How to make it to take a nap

Information

This method is originally explained in http://gentoo-wiki.com/HOWTO_HDD_spindown_small_server. I have just modified the needed scripts to work on MBWE.

 

There seems to be quite a few copies of earlier versions of this guide floating around the internets, so make sure you don't use outdated versions of the scripts. This page will always have the most up-to-date versions.

Prerequisites

Enabled SSH access.

A text editor and the know-how to use it. I recommend installing nano, but vi will also do just fine.

The how-to

  1. Make sure you are in superuser mode.

    # su
  2. Update hdparm. The default busybox version of hdparm is broken, it reports the state of the disks always as "active/idle", even if the disk is in standby mode.

     

    You can either build hdparm from the source or install a new version using optware. I you have updated your MBWE to firmware version 2.00.15 or newer, you probably don't have a c compiler anymore and your only option is to use the optware version.

     

    I haven't tried the optware version (8.5-1 as of writing) of hdparm myself, but other users have reported that it works just fine as long as you just remember to call it instead of the default one. The optware version doesn't overwrite the default one, so if you are calling hdparm directly without a path, you will most likely be calling the default one.

    Instructions on using optware can be found in the wiki. Just note that if you are using the optware version, you'll have to replace the path to hdparm in the beginning of the smart_spindown script with the path to the optware version of hdparm.

     

    And here's how to build it from source:

    Make a directory for source files

    # mkdir /tmp/src/
    # cd /tmp/src/

    Download and install hdparm

    # wget http://garr.dl.sourceforge.net/sourceforge/hdparm/hdparm-7.7.tar.gz
    # tar -zxvf hdparm-7.7.tar.gz
    # cd hdparm-7.7
    # make
    # make install

    You can then get rid of the source files

    # cd ..
    # rm -rf hdparm-7.7*

    Do not play with hdparm unless you really know what you are doing, you might end up with a damaged hard drive.

  3. Get the smart_spindown script, place it to /usr/sbin and its access rights

    # wget http://kyyhkynen.net/stuff/mybook/smart_spindown
    # mv smart_spindown /usr/sbin
    # chmod 0700 /usr/sbin/smart_spindown
    # chown root:root /usr/sbin/smart_spindown
  4. Then you should tinker with the settings in the beginning of the smart_spindown (see comments in the script for details). At this time just set the name(s) of your disk(s).

    You might want to first test drive the script with the logging output echoed to stdout. So don't touch the lines involving logging just yet.

    Also, leave the WAITTIME (= the amount of time in seconds of disk inactivity to wait before spinning the disks down) to a fairly small amount so you don't have to wait too long. 60 or 120 will do just fine, just set it to a longer period after you have tested that the script works. I have mine set to 300 (= 5 minutes).

    #!/bin/bash
    #
    # smart_spindown
    #
    # 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.
    #
    # Extended by joerk at gentoo-wiki.com
    # Heavily modified to fit WD MyBook World external hard drives by kyyhkynen at gmail.com
    # Last updated 2008/04/23
    #
    ###################################################################
    #
    # Configuration
    #
    # Disk(s) to monitor.
    # If you have only one disk, leave DISK2 empty
    DISK1=sda
    DISK2=sdb
    
    # hdparm location. If you are using the optware version, 
    # you have to change this into /opt/sbin/hdparm
    HDPARM=/sbin/hdparm
    
    # The base "no reads" wait time (in seconds). This is multiplied by
    # the backoff factor to determine the real "no reads" wait time.
    WAITTIME=300
    
    # 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 1200 seconds.
    MAXWAIT=1200
    
    # Time (in seconds) between polls to see if the disk is active again.
    # Default is 30 seconds.
    POLLTIME=30
    
    # Output levels. Level 2 is verbose, level 1 is normal output.
    # Enable all levels you would like to see.
    OUTLEVEL1=true
    OUTLEVEL2=true
    
    # Decide which output to use. Useful if run in daemon mode
    # echo or logger
    # output1 is logged to normal, output2 to debug syslog
    # Is PID of this shell logged?
    OUTPUT1=echo
    OUTPUT2=echo
    #OUTPUTTAG="$(basename -- $0)[$$]"
    #OUTPUT1="logger -t $OUTPUTTAG -p user.notice --"
    #OUTPUT2="logger -t $OUTPUTTAG -p user.debug  --"
    
    # Scripts to run whe the disk(s) are spinned up / down
    # If you dont  need this functionality, just leave them as they are.
    # The existence of these files is checked before they are run
    # Just make sure that the files are executable, if they exist
    EXECUTE_ON_SPINUP=/usr/sbin/smart_spindown_onspinup
    EXECUTE_ON_SPINDOWN=/usr/sbin/smart_spindown_onspindown
    
    
    #
    # End of configuration. You shouldn't have to change lines below this :)
    #
    #########################################################################
    
    # Device name(s) for the disk(s).
    DEVNAME1=/dev/$DISK1
    [ "$DISK2" ] && DEVNAME2=/dev/$DISK2
    [ -z "$DISK2" ] && DEVNAME2=
    
    # Stats file: the file used to monitor the disk's read activity.
    # The first entry in this stats file must represent the read activity.
    STATSFILE1=/sys/block/$DISK1/stat
    STATSFILE2=/sys/block/$DISK2/stat
    
    # Multiplication factor for the backoff after a spinup, in percentages.
    # Default is 300 = factor 3.
    BACKOFF_INCREASE_PCT=400
    
    # 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=98
    
    # 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=true
    
    
    
    #
    # 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=-1
    
    # 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
    
    # Log the end of script execution
    trap "$OUTPUT1 'Exiting.'" EXIT
    
    
    if [ "$DISK2" ] ; then
    	$OUTLEVEL1 && ${OUTPUT1} "Monitoring spindown opportunities for disks $DISK1 and $DISK2." ;
    else
    	$OUTLEVEL1 && ${OUTPUT1} "Monituring spindown opportunities for disk $DISK1." ;
    fi ;
    if ($OUTLEVEL1) ; then
    	$HDPARM -C $DEVNAME1 $DEVNAME2 |grep active >/dev/null
    	if [ "$?" == "0" ] ; then
    		[ "$DISK2" ] && ${OUTPUT1} "A drive is currently spun up." ;
    		[ -z "$DISK2" ] && ${OUTPUT1} "The drive is currently spun up." ;
    	else
    		[ "$DISK2" ] && ${OUTPUT1} "Both drives are currently spun down." ;
    		[ -z "$DISK2" ] && ${OUTPUT1} "The drive is currently spun down." ;
    	fi ;
    fi
    while [[ /sbin/true ]]; do
    	$HDPARM -C $DEVNAME1 $DEVNAME2 |grep active >/dev/null
    	if [ "$?" == "0" ] ; then
    		# the disks have spun up, run the spinup script
    		if [ -f $EXECUTE_ON_SPINUP ] ; then
                            $EXECUTE_ON_SPINUP ;
                    fi ;
    
    		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" -ge "0" ] ; then
    			$OUTLEVEL1 && ${OUTPUT1} "A drive spun up after $LASTDOWNTIME seconds. Total time up/down: $UPTIME/$(($POLLSSPUNDOWN*$POLLTIME)) (avg sleep time $(($POLLSSPUNDOWN*$POLLTIME/$SPINDOWNS)))" ;
    		fi
    		PREVIOUS_READS_DISK1=-1 ;
    		PREVIOUS_READS_DISK2=-1 ;
    		NEXT_READS_DISK1=-1 ;
    		NEXT_READS_DISK2=-1 ;
    		NUM_EQUALS=0 ;
    		$OUTLEVEL2 && ${OUTPUT2} "Waiting for $THISWAIT seconds of read inactivity..." ;
    		PREVIOUS_READS_DISK1=`cat $STATSFILE1 |awk '{ print $1; }'` ;
    		[ "$DISK2" ] && PREVIOUS_READS_DISK2=`cat $STATSFILE2 |awk '{ print $1; }'` ;
    		while [[ $(($NUM_EQUALS*5)) -lt $THISWAIT ]]; do
    			sleep 5 ;
    			UPTIME=$(($UPTIME+5)) ;
    			LASTUPTIME=$(($LASTUPTIME+5)) ;
    			NEXT_READS_DISK1=`cat $STATSFILE1 |awk '{ print $1; }'` ;
    			[ "$DISK2" ] && NEXT_READS_DISK2=`cat $STATSFILE2 |awk '{ print $1; }'` ;
    			if [[ $PREVIOUS_READS_DISK1 -ne $NEXT_READS_DISK1 ]] ; then
    				NUM_EQUALS=0 ;
    				PREVIOUS_READS_DISK1=$NEXT_READS_DISK1 ;
    				$OUTLEVEL2 && ${OUTPUT2} "$DISK1 read, restarting..." ;
    			else
    				if [ "$DISK2" -a $PREVIOUS_READS_DISK2 -ne $NEXT_READS_DISK2 ] ; then
    					NUM_EQUALS=0 ;
    					PREVIOUS_READS_DISK2=$NEXT_READS_DISK2 ;
    					$OUTLEVEL2 && ${OUTPUT2} "$DISK2 read, restarting..." ;
    				else
    					NUM_EQUALS=$(($NUM_EQUALS+1)) ;
    					$OUTLEVEL2 && ${OUTPUT2} "Seconds of quiet: $(($NUM_EQUALS*5))" ;
    				fi
    			fi
    		done
    		
    		# We are going the din the disk(s) down, run the spindown script
    		if [ -f $EXECUTE_ON_SPINDOWN ] ; then
                            $EXECUTE_ON_SPINDOWN ;
                    fi ;
    
    		# 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 $DEVNAME1 $DEVNAME2 ;
    		SPINDOWNS=$(($SPINDOWNS+1)) ;
    		$OUTLEVEL1 && ${OUTPUT1} "Drive(s) spun down after $LASTUPTIME seconds (with $THISWAIT seconds of inactivity), total spin down count: ${SPINDOWNS}." ;
    		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
    		${OUTPUT2} "spindowns: $SPINDOWNS, time up/down: $UPTIME/$(($POLLSSPUNDOWN*$POLLTIME)), backoff $BACKOFF_FACTOR, down for $LASTDOWNTIME (avg $(($POLLSSPUNDOWN*$POLLTIME/$SPINDOWNS)))." ;
    	fi ;
    	sleep $POLLTIME ;
    	LASTPOLLTIME=$POLLTIME ;
    done
    
  5. Now let's test the script. First, set the kernel parameters so that the disk writes will be cached. Later on, the service startup script will take care of these, but now that we are only testing, we'll have to set them manually.

    # echo 0 > /proc/sys/vm/dirty_expire_centisecs
    # echo 0 > /proc/sys/vm/dirty_writeback_centisecs
    # echo 95 > /proc/sys/vm/dirty_ratio
    # echo 10 > /proc/sys/vm/dirty_background_ratio
    # echo 5 > /proc/sys/vm/laptop_mode
    # echo 40 > /proc/sys/vm/swappiness

    If you are interested in how each of the paremeters affect the system, here is a brief explanation.

     

    Start the script, don't do anything else with the MBWE and watch the output. If you have the box nearby, you'll probably also hear the spin-downs and spin-ups.

    # /usr/sbin/smart_spindown

    Leave the script running for a couple of hours and you'll see if the disks spin up unintentionally. If they do, you'll probably have to reduce the disk usage somehow.

    With my MBWE it seems to take some time after a reboot for the script to start really working. So wait patiently. Leave the script running for example for a night. Even if it keeps spinning up and down, one night doensn't probably kill your disks, right? :)

  6. If the script works just fine, kill it by pressing CTRL+C.

    Now tweak the settings in the beginning of the script. Set the WAITTIME to serve your needs; I have mine set to 300 (= 5 minutes). Then set the MAXWAIT (= the amount of time in seconds of disk inactivity to wait at most, the time is changing dynamically between WAITTIME and MAXWAIT) accordingly.

    Also, mind the logging options. If you would like to log the output to syslog, uncomment the lines with logger. To disable logging and thus reduce disk activity, set both LOGLEVEL1 and LOGLEVEL2 to false.

    If you wan't to have the log available, but don't want to use the syslog (you're going to disable it in my next guide anyway :), see my simple alternative logging script in the end of the alternative temperature monitor guide.

  7. Next, we'll make the script start and stop automatically.

    Download the script to start/stop the service, put it into /etc/init.d/ and set its access rights. Then make a symbolic link to stop the service when the MBWE is shutting down.

    # wget http://kyyhkynen.net/stuff/mybook/S50smart_spindown
    # mv S50smart_spindown /etc/init.d	
    # chmod 0700 /etc/init.d/S50smart_spindown
    # chown root:root /etc/init.d/S50smart_spindown
    # ln -s /etc/init.d/S50smart_spindown /etc/init.d/K50smart_spindown

    The scripts in the directory /etc/init.d/ starting with S[number] are automatically run during system startup with parameter start. The number defines the order the scripts are run. Likewise, scripts starting with K[number] are run when the system is shutting down with parameter stop.

  8. This step is optional. If the script seems to work ok for you, you don't necessarily need to do this. You can try this also later if you find it necessary. Edit your fstab with caution; you might mess your drives up by playing around with it.

    Find this line in /etc/fstab:

    /dev/md3    /var    ext3    defaults,noatime    0   2

    Change it into this:

    /dev/md3    /var    ext3    defaults,noatime,commit=43200    0   2

    Changing this line causes the filesystem on /var (where log files and all frequently accessed stuff is located) to commit its journal to the disk only twice a day, thus reducing disk access.

  9. Reboot your MBWE.

    # reboot

    You will lose your SSH connection during the boot, so reconnect to the MBWE.

    The script should be now running. You can check it like this:

    # ps -A | grep smart
  10. You're done here. Enjoy your new sleepy MBWE.

    If you wan't to tweak your settings without rebooting the whole device, just make your changes to the script and restart it by

    # /etc/init.d/S50smart_spindown restart

    If your disk(s) keep spinning up randomly, see Monitoring disk usage for further tips.

  11. You probably noted the EXECUTE_ON_SPINUP and EXECUTE_ON_SPINDOWN variables in the configuration section of the script. You can use these to do stuff when the disk(s) are spinned up and down.

    Just point the variables to shell scripts you want to be run on spinup/spindown (make sure the scripts are set executable).

    For example, I have set it up to turn the front panel leds on and backup my RAM disk to disk whenever the disks are spun up and turn the leds off when the disks are spun down.

    Example spin up script:

    #!/bin/bash
    
    # back up the RAM disk
    /etc/init.d/S13ramdisk backup
    
    # turn on the front panel leds
    echo 255 > /sys/class/leds/wdc-leds:power/brightness
    echo 100 > /sys/class/leds/wdc-leds:fuel-gauge/brightness
    

    Example spin down script:

    #!/bin/bash
    
    # turn off the front panel leds by settings their brightness to zero
    echo 0 > /sys/class/leds/wdc-leds:power/brightness
    echo 0 > /sys/class/leds/wdc-leds:fuel-gauge/brightness