#!/usr/bin/perl -w # By Marc MERLIN # $Id: swdisksusp 353 2011-09-24 20:52:16Z svnuser $ # Home page: # http://marc.merlins.org/perso/linux/post_2010-08-03_Spinning-Down-WD20EADS-Drives-and-Fixing-Load-Cycle.html # Manually spin down drives that will not spin down with hdparm -S x /dev/device # like they should. # This script is meant to be run as "swdisksusp &" as it logs to stderr It # sleeps 10 seconds between each try, so it's kind of pointless to set the # timeout to less than 10 seconds, which you wouldn't want to do for spinning # down anyway. # iostat is required as well as kernel support for it before this script # will work. # Western Digital green drives like the WD20EADS are affected by this. They # are so busy trying to park their heads every 6 seconds (watch Load Cycle in # SMART) that their firmware seems to ignore the -S timeout value altogether # (incidently both for parking heads or spinning down the platters :( ). # If your system tickles the drive in any way (SMART query, including hddtemp, # or apparently LVM or software raid or something else on my system woke the # drives up constantly), your Load Cycle count will shoot through the roof (up # to 2000/day for me, or about 1.5 years of expected parking/unparking lifetime # with a limit of about 1 million). Note that in my case I was not keeping the # filesystem busy since iostat did show no disk access and spinning down the # drives with hdparm -y /dev/device showed that they stayed spun down for 10 to # 20mn at a time. # It's not entirely clear why those drives seem to unpark when no data # is requested from them. # You can check Load Cycle Count with: # smartctl -A /dev/device | grep Load_Cycle_Count # To stop this unfortunate behaviour, you need to run the unapproved but working # wdidle3 from http://support.wdc.com/product/download.asp # (yes it says it's not supported on all but 3 drives, but it has worked for me and others. # I personally set /S300 and it has stopped my parking). use strict; my $time = time; # BEGIN Variables to set are my %watchdevs = ( "sdh" => $time, "sdi" => $time, "sdj" => $time, "sdk" => $time, "sdl" => $time, ); # How many seconds of inactivity before we spin down a drive # you can override on the command line my $sleep_timeout = 300; # END Variables to set are my $stderr = ""; my $DEBUG = 0; if (defined $ARGV[0] and $ARGV[0] eq "--debug") { $DEBUG = 1; $stderr = "-s"; shift @ARGV; } $sleep_timeout = $ARGV[0] if (defined $ARGV[0] and $ARGV[0]); print STDERR "Will monitor ".join(", ", keys (%watchdevs))." and spin them down after ${sleep_timeout}s of inactivity\n"; while (1) { # Show 2 seconds of iostat, and skip the first run that shows init counters # Any devices shown in the subsequent run saw data traffic. open(IOSTAT, "iostat -z 1 2 |"); # Skip first 5 lines of headers. for (my $i=1; $i<5; $i++) { $_ = ; } # and keep looking for output until the the first batch of drives is fully shown. while () { last if /^avg-cpu:/; } # avg-cpu: %user %nice %system %iowait %steal %idle # 18.30 0.00 5.51 1.50 0.00 74.69 # # Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn # sdh 3.00 0.00 4.00 0 4 # sdi 3.00 0.00 4.00 0 4 # sdj 8.00 48.00 52.00 48 52 # sdk 8.00 48.00 52.00 48 52 # sdl 3.00 0.00 4.00 0 4 # md7 7.00 0.00 48.00 0 48 # dm-2 6.00 0.00 48.00 0 48 $time = time; # Grab all the device names that saw any update (read or write) since init. while () { my $dev; /^([hs]d\S)\s+/ or next; $dev = $1; #warn "Testing $dev at $time\n"; # $watchdevs{$dev} contains a positive time for a last update with the drive running. # and a negative time if that last update was before the drive was put to sleep. if (defined $watchdevs{$dev}) { system("logger $stderr \"$dev came out of sleep after ".($time + $watchdevs{$dev})."s of inactivity\"") if ($watchdevs{$dev} < 0); $watchdevs{$dev} = $time; } } close(IOSTAT); foreach my $dev (keys %watchdevs) { if ($watchdevs{$dev} > 0 and $time - $watchdevs{$dev} > $sleep_timeout) { if ($stderr) { system("hdparm -y /dev/$dev"); } else { system("hdparm -y /dev/$dev &>/dev/null"); } system("logger $stderr \"Putting $dev to sleep after ${sleep_timeout}s of inactivity\""); $watchdevs{$dev} = -$time; } } sleep 10; } # vim:sts=4:sw=4