How to make hard drive standby (spin down) on a timer?

I'd like to have my hard drives spin down when idle for, let's say 20 minutes. Any number of minutes is fine, as this NAS is rarely used.

What I've tried and didn't work:

  • ataidle -S 20 /dev/ada0 simply spins down the drive immediately and the timer has no effect. Once drives are spun back up due to an access they stay spinning.

  • camcontrol standby /dev/ada0 -t 1200 same behavior as ataidle.

  • FreeNAS UI's Storage -> Disks -> Adv. Power Manager setting simply calls camcontrol and similarly the timer has no effect. If a power setting that allows standby is selected (such as 127) then drives spin down nearly immediately (potentially after 8 seconds), and get constantly spun up and down if there are any accesses. UPDATE: See How to get FreeNAS to spin down disks? for instructions on how to make this work and skip the manual scripts.

How can I get the normal expected "stand by if not used in a while" behavior?

Using FreeBSD 11.2-STABLE via FreeNAS 11.2. Drives are 4x Samsung 2TB 2.5" .T2000LM003`

# smartctl -P show /dev/ada0
smartctl 6.6 2017-11-05 r4594 [FreeBSD 11.2-STABLE amd64] (local build)
Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org

Drive found in smartmontools Database.  Drive identity strings:
MODEL:              ST2000LM003 HN-M201RAD
FIRMWARE:           2BC10007
match smartmontools Drive Database entry:
MODEL REGEXP:       ST(1500|2000)LM0(03|04|06|07|10) HN-M[0-9]*RAD
FIRMWARE REGEXP:    .*
MODEL FAMILY:       Seagate Samsung SpinPoint M9T
ATTRIBUTE OPTIONS:  None preset; no -v options are required.

Solution 1:

Thanks for this idea. I had been struggling with this problem for a few days. As I am not used to write bash scripts I rewrote it in python3. Maybe somebody finds it useful:

import subprocess
import time

drives_to_check = ['ada3', 'ada4', 'ada5']
no_sleep_hours = ['00', '01']
seconds = 600

if time.strftime("%H") in no_sleep_hours:
    exit()

o = subprocess.check_output(f'/usr/sbin/iostat -x -z -d {seconds} 2', shell=True)

for drive in drives_to_check:
    if drive not in o.decode().split('extended')[-1]:
        p = subprocess.check_output(f'/sbin/camcontrol cmd {drive} -a "E5 00 00 00 00 00 00 00 00 00 00 00" -r -', shell=True)
        if p.decode()[27:29] != '00':
            q = subprocess.check_output(f'/usr/local/sbin/ataidle -s /dev/{drive}', shell=True)

My small add-ons were: you can add hours to no_sleep_hours when you don't want the script to run. And you have to add the drives you want to check to "drives_to_check" so you can exclude drives that should not do a spindown. Maybe you would have to adjust the paths for iostat and ataidle, these are the paths used in FreeNAS. You can find your paths with: which adaidle or which iostat. You can add it to cron with */15 * * * * /usr/local/bin/python3 /path/to/the/folder/check_disk_usage_and_spindown.py

Solution 2:

UPDATE: See https://serverfault.com/a/965694/184095 for how to make the GUI work and skip the manual scripting process below.


I ended up doing this very manually. Write a script that checks for drive activity, spins down drives if there is none, then schedule this script as a cron job.

I settled on an approach that uses iostat to monitor drives for a set period of time, in my case 600 seconds (10 minutes). If iostat reports no usage, call ataidle -s /dev/ada[0123] to suspend the drives. I setup a cron job to call this script every 15 minutes: */15 * * * * spindown_if_idle.sh 600

spindown_if_idle.sh:

#!/bin/bash

# argument is the number of seconds to test for drive use, default to 5
if [ -z "$1" ]; then
    SECONDS=5
else
    SECONDS="$1"
fi

function list_ada_drives {
    # emit a list of hard drives, i.e. ada0 ada1 ada2 ...
    iostat -x | grep "ada" | awk '{print $1}'
}

function spindown {
    # for every hard drive use ataidle to place it in standby
    for ADA in $(list_ada_drives); do
        ataidle -s /dev/$ADA
    done
}

function are_drives_used {
    # argument is number of seconds to test over

    # run iostat for the specified number of seconds and ask to report any usage
    # -z means to omit any drives that are idle
    # iostat will print two reports, the first since boot and the second during the interval
    # The first tail and grep strip off the first useless report. The second tail strips off the header
    # The final grep strips off drives we're not interested in
    iostat -x -z -d $SECONDS 2 | tail -n +2 | grep -A5000  "extended" | tail -n +3 | grep "ada" -q
}

if are_drives_used $SECONDS ; then
    echo "Drives are being used"
else
    echo "Drives are idle, going to standby"
    spindown
fi

collectd metrics

I tried avoiding that 10-minute block by querying historic usage as recorded by collectd. I used rrdtool fetch to query the metrics stored in /var/db/collectd/rrd/localhost/disk-ada0, but those have a lag of several minutes. Relying on them would mean that the script might standby drives that are actively used if they had been idle recently.

Testing if drives are spun up

The following script will report if each drive is idle or spinning. Useful for testing.

is_spinning.sh:

#!/bin/sh

camcontrol devlist | grep ada | awk -F\( '{print $2'} | awk -F",|\\\\)" '{print $2}' |while read LINE
do
CM=$(camcontrol cmd $LINE -a "E5 00 00 00 00 00 00 00 00 00 00 00" -r - | awk '{print $10}')
if [ "$CM" = "FF" ] ; then
echo "$LINE: SPINNING"
elif [ "$CM" = "00" ] ; then
echo "$LINE: IDLE"
else
echo "$LINE: UNKNOWN"
fi
done

Based on this forum post.