#!/bin/bash

# Written by Thomas Backman <serenity@exscape.org>, 2009-04-17 (evening) - 2009-04-18 (morning) ;)
# License: BSD. Basically, do what you want but credit me even if you modify it quite heavily.

# Requirements: Not sure, but Linux for sure. Most likely 2.6.
# sysfs (i.e. /sys).
# hdparm.

# Usage: spindownmonitor.sh <device>, e.g. spindownmonitor.sh sda
# I put "spindownmonitor.sh sdb &" in /etc/conf.d/local.start on my Gentoo box. Anything that runs on startup is fine.

# Settings:

# How often we poll the data to see if the disk is idle, and in effect, how often it may spin down. If a low
# value is used, the drive might spin up and down a lot if used, which will lower the lifespan of the drive. I suggest
# monitoring how often the drive switches between standby and active/idle for a few days.
# This should be at least 600 (10 minutes), but I suggest at least 900-1800 (15-30 minutes). 
# In my case, if the disk isn't used in 15 minutes, it's unlikely to be used for hours if not days, so I use a low-ish value.
CHECKTIME=900

###### Nothing to change below this line ######

if [ -z "$1" ]; then
	echo "Error: first argument must be a disk name (e.g. sda). Further arguments are ignored." 1>&2
	exit 1
fi

DISK=$1

if [[ ! -b "/dev/$DISK" ]]; then
	echo "Error: Device /dev/$DISK not found or not a block device, exiting $0" 1>&2
	exit 1
fi

STATSFILE=/sys/block/$DISK/stat

function getreads() {
	awk '{print $1}' $STATSFILE
}

function getwrites() {
	awk '{print $5}' $STATSFILE
}

READ=0
WRITTEN=0
LASTREAD=$(getreads)
LASTWRITTEN=$(getwrites)

while :; do
	sleep $CHECKTIME

	# Check if the drive is active or not
	hdparm -C /dev/$DISK | grep -q 'active/idle'
	if [[ "$?" != "0" ]]; then
		# Disk is already asleep, skip checks
		continue
	fi

	LASTREAD=$READ
	LASTWRITTEN=$WRITTEN
	READ=$(getreads)
	WRITTEN=$(getwrites)

	# If no IO requests, read or write, have been issued since the last CHECKTIME, put the drive to sleep
	if [[ $READ = $LASTREAD && $WRITTEN = $LASTWRITTEN ]]; then
		hdparm -y /dev/$DISK > /dev/null 2>&1
	fi
done
