2

I have 2 external WD Red hard disks connected to a Raspberry Pi.
They spin down after 2 minutes, which is annoyingly fast and I always have to wait for them to spin up.

I've read that hdparm doesn't work with WD disks. I've tested hdparm -I | grep level and didn't get any output.

I've read about hd-idle, but from what I understand, it helps with disks that never go into standby.

What can I do to increase standby timeout?

edit: I've tested hdparm -S, zero effect.

  • It maybe a dumb question...do you have enough power for driving both of them? – Rui F Ribeiro Feb 3 '18 at 15:52
  • 1
    @RuiFRibeiro Not a dumb question at all. The disks work great otherwise. They are in a powered dock. – markonius Feb 3 '18 at 15:55
  • Look at hdparm -S 240 for a 20 minute run timeout, though the immediate side-effect is to idle the motor. (-C reads current state idle/run). – meuh Feb 3 '18 at 16:07
  • @meuh please, see edit – markonius Feb 3 '18 at 22:08
  • hdparm has a -J option for WD green, which probably applies to red. It is to set the idle3 time until the head parks (which is not necessarily the same as the motor stopping). The default is supposed to be 8 seconds, but you could try setting the max 300. That might push back the spin idle time to 5 minutes too. Parking just moves the heads from the surface to reduce power consumption a bit. – meuh Feb 5 '18 at 19:27
0

You can write your own version of hd-idle that does the opposite, i.e. does a keep-awake by doing some access on the disc drive before it would normally want to idle. But you obviously need to distinguish when to really let go and have the disc sleep.

Here's an attempt with a perl script. You don't need to be root to run it, but you do need to setup some file on the disc that you can read to do the keep-awake. Like hd-idle it polls the disc read/write statistics from /sys/. When no i/o has been done for the appropriate time, a small read is done from the keep-awake file using flag O_DIRECT to ensure a real i/o is done. This should just add 1 to the read/write statistics total, so we can detect if some other real i/o has also happened next poll.

#!/usr/bin/perl
# stop standby of idle disc which doesnt support hdparm -S 12
# for stat fields see kernel src Documentation/iostats.txt
# https://unix.stackexchange.com/a/422138/119298
use strict;
use Fcntl;

my $device = 'sda';
my $filename = '/mnt/myfs/lost+found/tickle';

# create tickle file for test. or use any readonly non-empty file
if(! -s $filename){
    open(FILE,">$filename") or die $!;
    print FILE "used to keep disk from sleeping\n" or die $!;
    close(FILE) or die $!;
}
sysopen(FILE,$filename,O_RDONLY|O_DIRECT) or die $!;

my $minidle = 2*60;  # seconds of real idle before need to tweak awake
my $maxidle = 10*60; # seconds of false idle before allow to sleep
open(STAT,"/sys/block/$device/stat") or die;
my ($lasttot,$timechanged,$done);

# create block aligned buffer for O_DIRECT read
my $bufsize = 4096;
my $align = 512;
my $buf = 'x' x ($align+$bufsize);
my $offset = unpack("J", pack "p", $buf) % $align;
$offset = $align-$offset if $offset;

while(){
    my @fields = split(' ',<STAT>);
    # fields: 0 reads completed ok, 4 writes completed ok.  may wrap
    my $tot = $fields[0]+$fields[4];
    if($tot==$lasttot){
        my $idle = time()-$timechanged;
        print "$device idle $idle secs\n";
        if($done){
            # let it sleep some more
        }elsif($idle>=$maxidle){ # let it really sleep now
            print "$device sleep now after $idle secs\n";
            $done = 1;  
        }elsif($idle>=$minidle){ # tickle to stay awake
            sysseek(FILE,0,Fcntl::SEEK_SET)==0 or die $!;
            sysread(FILE,$buf,$bufsize,$offset)>0 or die $!;
            $lasttot++;
        } # else builtin hardware timeout not reached yet
    }else{
        $timechanged = time();
        $lasttot = $tot;
        $done = 0;
    }
    seek(STAT,0,0);
    sleep(55);
}

You need to set $device to the name of the disc, and $filename to the name of an existing non-empty file, or of a file that can be created on the disc. In $minidle we need the time when the disc normally goes idle on its own, and in $maxidle the time we want to
force it to stay active. The poll time is set in the sleep() at the end.

|improve this answer|||||
  • Thanks! This is a detailed solution. However, I refuse to believe WD Red disks are designed to go to standby after 2 mins, since they are marketed as "lite server" disks. I will first try connecting them to another computer via USB and SATA if necessary and do some testing. – markonius Feb 7 '18 at 7:39
  • There's some discussion of WD Red here, and some blurb from WD on their nasware firmware. – meuh Feb 7 '18 at 8:40
  • You've been a great help, I appreciate it. It seems these drives are indeed designed to sleep quickly. I will try your solution in a few days and will mark it as an answer accordingly. – markonius Feb 7 '18 at 11:21
0

Although meuh's answer is the right answer for this question, my actual situation involves software raid and me being a filthy millenial.
I reimplemented meuh's solution in Python using writes instead of reads.

import time
import datetime
import os

statFile = "/sys/block/<yourdevicehere>/stat"
logFile = "/some/file/on/another/drive.log"
dummyFile = "/some/file/on/drive/in/question"
updateInterval = 60 # seconds
keepAwakeTime = 30 * 60 # seconds
keepAwakeIOCount = 12 # empirically "proven"

# Clear the log file
open(logFile, "w").close()

def getIOCount():
    with open(statFile, "r") as fobj:
        fileContents = fobj.read()
    stats = fileContents.split()
    readCount = int(stats[0])
    writeCount = int(stats[4])
    return readCount + writeCount

# ==== main loop ==== #

maxIODiff = 0
averageIODiff = 0
diffCount = 0

previousIOCount = getIOCount()
timer = 0
while True:
    ioCount = getIOCount()
    if ioCount > previousIOCount:
        timer = keepAwakeTime

    ioDiff = ioCount - previousIOCount
    if ioDiff > maxIODiff:
        maxIODiff = ioDiff
    averageIODiff = (averageIODiff * diffCount + ioDiff) / (diffCount + 1)
    diffCount += 1

    previousIOCount = ioCount

    if timer > 0:
        line = str(ioDiff) + " " + str(maxIODiff) + " " + str(averageIODiff) + " " + datetime.datetime.now().strftime("%H:%M:%S") + "\n"
        with open(dummyFile, "w") as dummy:
            dummy.write(line)
            dummy.flush()
            os.fsync(dummy.fileno()),
        with open(logFile, "a") as log:
            log.write(line)
        previousIOCount += keepAwakeIOCount

    timer -= updateInterval
    time.sleep(updateInterval)

Event though this one is technically less correct than meuh's solution, I can't parse Perl and it seems to work just fine on my setup. And I felt like coding it on my own.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.