Skip to main content

Making inactve USB Hard Disk spin down automatically in Linux.

I have a 400GB Seagate IDE HDD connected to Mars, our hostel's file-server using an USB enclosure. The USB enclosure is a cheap "Made in China" product. Consequently it has some special "features". One such notable "feature" is that the disk is kept spinning by the controller even if there has been no disk I/O for a long time. I have three other USB disks connected to the same machine, a 1TB Seagate FreeAgent Desk External Drive, a 500GB Maxtor Basics External Drive and a 2.5" 60GB Fujitsu SATA Disk inside a Transcend USB enclosure. All of these spin down themselves if there has been no I/O for sometime. Keeping the hard disk spinning unnecessarily for ever, not only wastes power but also overheats the drive, thereby reducing its life.

I tried noflushd, which is supposed to force idle hard disks to spin down, but found it to be of no help. USB enclosure generally work by performing an SCSI emulation over USB. sdparm is an utility which can be used to send simple SCSI commands. A peep into its manpage revealed that the disk could be ordered to spin down by sudo sdparm -C stop /dev/sdb1 where sdb1 is the required disk (the disk has a single partition, "sdparm -C stop /dev/sdb1" and "sdparm -C stop /dev/sdb" did the same thing here, however if there are multiple partitions, it is more meaningful to specify "/dev/sdb" rather than "/dev/sdb1", since it is the disk that stops spinning).

However I need to do this automatically whenever the disk is idle. First, it is necessary to check whether the disk is active or idle. Info about disk I/O is available from /proc/diskstats.
cat /proc/diskstats | grep sdb1
shows info about sdb1, the output is something line this:
8 17 sdb1 210583 56943 24739612 2328860 11777 24804 292648 69260 0 1209770 2397450

It has several fields after "sdb1", denoting the following:
Field 1 -- No. of reads issued
Field 2 -- No. of reads merged
Field 3 -- No. of sectors read
Field 4 -- No. of milliseconds spent reading
Field 5 -- No. of writes completed
Field 6 -- No. of writes merged
Field 7 -- No. of sectors written
Field 8 -- No. of milliseconds spent writing
Field 9 -- No. of I/Os currently in progress
Field 10 -- No. of milliseconds spent doing I/Os
Field 11 -- weighted No. of milliseconds spent doing I/Os


Field 9 is of importance here, a nonzero value indicates disk activity. Any particular field can be dug out easily using grep and AWK (mawk interpreter is the obvious choice here).
cat /proc/diskstats | grep $DISKNAME | mawk '{ print $(NF-2) }' does the trick.
mawk '{ print i }' prints the ith field.
The variable NF is equal to the number of fields. We are interested in the field 3rd from the end. Hence $(NF-2).
Now this is probabilistic, if there is no I/O in that particular instant
cat /proc/diskstats | grep $DISKNAME | mawk '{ print $(NF-2) }'
will yield 0. To ensure that the disk is really inactive, the check has to be carried out quite a few times.

There is another pitfall, there are 4 USB disks connected to the machine. Which one would be named sdb is not fixed. At every reboot this may change. On the other hand uuid of a disk never changes (unless the partition table is modified). So the name of the disk has to determined from its uuid. grep, AWK, and sed comes to our rescue once again.
DISKNAME=`ls -l /dev/disk/by-uuid/ | grep "c5df6a02-b7a6-4f39-ad26-7eb915b76709" | mawk '{ print $(NF) }' | sed s_\.\.\/\.\.\/__`
What this essentially does is explained below.
"ls -l /dev/disk/by-uuid/" gives the following output

sambit@mars:~$ ls -l /dev/disk/by-uuid/
total 0
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 0db6e806-8d4b-4968-8ccc-c00af59bb065 -> ../../sdc1
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 4294e561-df28-4ddd-a689-0aba31d4d663 -> ../../sda2
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 4cb989c6-87b7-4179-bb4d-81b2b0193ab2 -> ../../sdd1
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 4cf2093e-f08c-4127-ae75-fff11edd81ae -> ../../sdd2
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 87ba9975-056d-4635-85e4-53f1c76d57fb -> ../../sda3
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 a74b47fb-a33c-4a5a-ad62-0bc831f6ffda -> ../../sdd3
lrwxrwxrwx 1 root root 10 2009-04-07 14:20 af0c007b-a15c-4661-84eb-7dc689dec861 -> ../../sda1
lrwxrwxrwx 1 root root 10 2009-04-10 22:09 c5df6a02-b7a6-4f39-ad26-7eb915b76709 -> ../../sdb1


ls -l /dev/disk/by-uuid/ | grep "c5df6a02-b7a6-4f39-ad26-7eb915b76709"
narrows it to:
lrwxrwxrwx 1 root root 10 2009-04-10 22:09 c5df6a02-b7a6-4f39-ad26-7eb915b76709 -> ../../sdb1


ls -l /dev/disk/by-uuid/ | grep "c5df6a02-b7a6-4f39-ad26-7eb915b76709" | mawk '{ print $(NF) }'
prints the last field:
../../sdb1

Finally sed puts some finishing touches, by replacing "../../" in "../../sdb1" with "".

ls -l /dev/disk/by-uuid/ | grep "c5df6a02-b7a6-4f39-ad26-7eb915b76709" | mawk '{ print $(NF) }' | sed s_\.\.\/\.\.\/__

sdb1

So putting everything together, the script at-last looks something like this:

#################################################################################################################################
#!/bin/bash
DISKNAME=`ls -l /dev/disk/by-uuid/ | grep "c5df6a02-b7a6-4f39-ad26-7eb915b76709" | mawk '{ print $(NF) }' | sed s_\.\.\/\.\.\/__`
let a=0
#check 100 times with 0.1s gaps,
#and go on adding
for i in `seq 0 100`
do
let a=`cat /proc/diskstats | grep $DISKNAME | mawk '{ print $(NF-2) }'`+a
sleep 0.1s
done
echo $a
if [ $a == 0 ]
then
echo "No Activity"
sdparm -C stop /dev/$DISKNAME
else
echo "Disk Active"
fi
exit 0
#################################################################################################################################

I added this to crontab and made it run hourly. The hard disk can rest in peace for sometime in between heavy work now.

Comments

Anonymous said…
Awesome dude, thanks for the script! That would have taken me ages to figure out and write!
Daniel Rooney said…
Thanks for this command script. I may need to use a Vsim in order not to mess up my memory allocation.
document storage
natmaka said…
Another attempt (Perl): http://www.nslu2-linux.org/wiki/FAQ/SpinDownUSBHarddisks#method3
Letho said…
Great! Already deployed on my Pi! :)
Letho said…
Great! Already deployed to my Pi! :)
I had some problems with the script you proposed, so I adapted it to check every 5 min if the status of the disk is idle and in case it remains idle for 10min it is spinned down.
The script can be added to rc.local with the command
bash /etc/init.d/spindown.sh >> /var/log/spin.log &

I hope it helps.

Francesco

spindown.sh
###########################################################################################
#!/bin/bash
DISKNAME=`ls -l /dev/disk/by-uuid/ | grep "B4A02C87A02C5262" | mawk '{ print $(NF) }' | sed s_\.\.\/\.\.\/__`
#echo $DISKNAME
echo -e `date` SPINDOWN START FOR $DISKNAME
let a=0
let active=1
ameno1=$a
#and go on adding
while true; do
ameno2=$ameno1
ameno1=$a
let a=`cat /proc/diskstats | grep $DISKNAME | mawk '{ print $4 }'`
#echo -e -n `date` $a "\t" $ameno1 "\t" $ameno2 "\t"
let read=$a-$ameno1+$ameno1-$ameno2
if [ $read == 0 ]
then
if [ $active == 1 ]
then
echo -e `date` $a "\t" $ameno1 "\t" $ameno2 "\t" "Disk Spin Down"
sdparm -q -C stop /dev/$DISKNAME
active=0
fi
else
echo -e `date` $a "\t" $ameno1 "\t" $ameno2 "\t" "Disk Active"
active=1
fi
sleep 300s
done
exit 0
##########################################################################################

Popular posts from this blog

Force an application to use VPN, using iptables in Linux

Enforcing an application, for example a torrent client like Transmission, to always use the VPN interface or any particular network interface for that matter, is trivially simple using iptables on Debian, Ubuntu or any other GNU/Linux distro.
Personally, I am running Debian Sid on the Raspberry Pi. Occasionally I use it for downloading files ( legal stuff, seriously, believe me :D  ) using Transmission Bittorrent client over a VPN connection. Sometimes it happens that the VPN connection fails and doesn't reconnect for whatever reason and Transmission continues pulling stuff directly over my internet connection, which I would like to avoid. Fortunately it is very straightforward to enforce rules based on application owner UID. Transmission runs under the owner debian-transmission in Debian (use htop to check this) and the following two lines of iptables ensures that any process with owner having UID, debian-transmission, will not use any other network interface apart from the Open…

Some more fun with SSH port forwarding and socks proxy

Few days ago I made the following post:

Prologue: Our Institute has several nice Dual Core Machines deployed for the students. Unfortunately the machines are behind a NAT with no port forwarded for external SSH access. Student's hostel is a bit far off from the computational centre. As such if someone felt the need of accessing the machines during non-office hours, it was a wee bit difficult. The sysadmin would not have agreed to forward any ports. Something had to be done....

SSH has a very useful feature - Remote and Local Port Forwarding. We have an old rickety PIII running Ubuntu 8.04.1 in the Hostel, it is connected to the net and is accessible via SSH from the internet. Using a tiny little shell script running on one of the machines in the Institute, I managed to make the old PIII an intermediate gateway for gaining SSH access to the Institute's machines from anywhere in the internet. The script is of few lines, but nevertheless powerful enough to serve our purpose.

#!/bin/…

Raspberry Pi -- Installing Samba (Windows Share) File server

Having successfully run Debian Wheezy on my Raspberry Pi, I went forward with my initial idea of setting up a low cost power efficient file server for accessing my external hard disks from my Windows7 desktop, HP-Mini running Ubuntu and Mac Mini running OS X Lion (yeah I do like bragging about my machines :D ).

This turned out to be pretty straight forward.

As expected, the external Seagate USB disk immediately got recognized and appeared as /dev/sda
[ 579.948350] usb 1-1.2: New USB device found, idVendor=0bc2, idProduct=3001 [ 579.948384] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 579.948405] usb 1-1.2: Product: FreeAgent [ 579.948421] usb 1-1.2: Manufacturer: Seagate [ 579.948447] usb 1-1.2: SerialNumber: 2GEX323R [ 579.967638] scsi0 : usb-storage 1-1.2:1.0 [ 580.970520] scsi 0:0:0:0: Direct-Access Seagate FreeAgent 102D PQ: 0 ANSI: 4 [ 589.142942] sd 0:0:0:0: [sda] 1953525168 512-byte logical blocks: (1.00 TB/931 GiB) [ 589.144669] sd…