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 asataidle
. -
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.