From 69d241362d4e45e7ce52136122fb116a2a640933 Mon Sep 17 00:00:00 2001 From: Björn Persson Date: Sun, 18 Mar 2018 08:16:40 +0100 Subject: imported --- smart_spindown | 246 ++++++++++++++++++++++++++++++++++++++++++++++++ smart_spindown@.service | 16 ++++ 2 files changed, 262 insertions(+) create mode 100755 smart_spindown create mode 100644 smart_spindown@.service diff --git a/smart_spindown b/smart_spindown new file mode 100755 index 0000000..9f7a3cb --- /dev/null +++ b/smart_spindown @@ -0,0 +1,246 @@ +#! /bin/bash +# +# smart_spindown rev. 1 +# +# Copyright (C) 2003 by Bart Samwel +# +# You may do with this file (and parts thereof) whatever you want, as long +# as my copyright notice is retained. +# +# Extended by joerk at gentoo-wiki.com +# modified for a mostly idle backup disk by B Persson +# +# +# A command line parameter --conf= is required. The specified file +# will be sourced as shell code. It must set the variable DISK to the pathname +# of the disk to be monitored. It is recommended to use one of the links under +# /dev/disk. +# +# How it works: This program monitors the activity on a disk. If there +# is no activity for a while, the disk is spun down. The time without +# activity that is required is dynamic, using a backoff factor. When +# the recent spun-down periods are relatively short, this means that the +# machine might be busy with something, so the script tries to wait for +# longer periods without activity before spinning down again. When spun-down +# periods are long, the backoff factor is decreased, and the disk is spun +# down after shorter periods without activity. + + +# +# Configuration +# + +# Output levels. Level 2 is verbose, level 1 is normal output. +# Enable all levels you would like to see. +OUTLEVEL1=true +OUTLEVEL2=false + +# Decide which output to use. Useful if run in daemon mode +# echo or logger +# output1 is logged to normal, output2 to debug syslog +# Is PID of this shell logged? +#OUTPUT1=echo +#OUTPUT2=echo +OUTPUTTAG="$(basename -- $0)[$$]" +OUTPUT1="logger -t $OUTPUTTAG -p user.notice --" +OUTPUT2="logger -t $OUTPUTTAG -p user.debug --" + +# Multiplication factor for the backoff after a spinup, in percentages. +# Default is 300 = factor 3. +BACKOFF_INCREASE_PCT=300 + +# Multiplication factor for the backoff at every poll that shows that +# the disk is spun down. This determines how fast the backoff value +# decreases. +BACKOFF_DECREASE_PCT=96 + +# The base inactivity wait time (in seconds). This is multiplied by +# the backoff factor to determine the real inactivity wait time. +WAITTIME=150 + +# The maximum inactivity wait time (in seconds). +# This also limits the backoff factor: the backoff factor cannot increase +# above a value that makes the inactivity wait time larger than MAXWAIT. +# Default is 120 seconds. +MAXWAIT=1200 + +# Time (in seconds) between polls to see if the disk is active again. +# Default is 10 seconds. +POLLTIME=10 + +# Enable this if you don't use laptop_mode. This will make the script +# sync before spinning down the disk. +NO_LAPTOP_MODE=true + + +# +# Parse the command line: +# + +if [[ $# -ne 1 ]] ; then + echo 'Exactly one parameter is required, namely --conf=.' >&2 + exit 1 +fi + +case "$1" in + --conf=*) + CONF="${1#*=}" + ;; + *) + echo 'unrecognized parameter' >&2 + exit 1 + ;; +esac + + +# +# Read the configuration: +# + +source "$CONF" +if [[ $? -ne 0 ]] ; then + echo 'Reading the configuration file failed.' >&2 + exit 1 +fi + +# Get the device name of the disk to monitor: +DEVNAME=`realpath "$DISK"` +if [[ $? -ne 0 ]] ; then + echo 'bad disk pathname' >&2 + exit 1 +fi +if [[ ! -b "$DEVNAME" ]] ; then + echo 'The disk pathname is not a block device.' >&2 + exit 1 +fi + +# Stats file: the file used to monitor the disk's activity. +STATSFILE=/sys/block/`basename "$DEVNAME"`/stat + + +# +# Let's go! +# + +# Number of poll times that the disk was found to be spun down. +POLLSSPUNDOWN=0 + +# Number of spindowns performed +SPINDOWNS=0 + +# Number of times (*100) the WAITTIME of inactivity required before spindown +BACKOFF_FACTOR=100 + +# Stats: Total time the disk has been up. +UPTIME=0 + +# Total duration of last spun-down period. +LASTDOWNTIME=0 + +# Total duration of the last spun-up period. +LASTUPTIME=0 + +# Duration of the last poll. Always equal to POLLTIME except the first +# time around. +LASTPOLLTIME=0 + +# Make sure the stuff we use is in the cache. I've seen it happen +# that the script spun the disk down, and then "sleep" wasn't in +# the cache and the disk spun right up again. :) +true +false +sleep 1 + +# Terminate on request. +trap 'exit 0' HUP INT TERM + +# Log the end of script execution. +trap "$OUTPUT1 'Exiting.'" EXIT + + +$OUTLEVEL1 && ${OUTPUT1} "Monitoring spindown opportunities for $DEVNAME." +if ($OUTLEVEL1) ; then + hdparm -C $DEVNAME |grep active >/dev/null + if [ $? -eq 0 ] ; then + ${OUTPUT1} "$DEVNAME is currently spun up." ; + else + ${OUTPUT1} "$DEVNAME is currently spun down." ; + fi ; +fi +while [[ true ]]; do + hdparm -C $DEVNAME |grep active >/dev/null + if [ $? -eq 0 ] ; then + THISWAIT=$(($WAITTIME*$BACKOFF_FACTOR/100)) ; + if [[ $THISWAIT -gt $MAXWAIT ]] ; then + THISWAIT=$MAXWAIT ; + fi ; + # Increase the backoff irrespective of whether we failed + # or not. The backoff should drop again by the lack of + # spinups afterwards. + BACKOFF_FACTOR=$(($BACKOFF_FACTOR*$BACKOFF_INCREASE_PCT/100)) ; + if [[ $(($BACKOFF_FACTOR*$WAITTIME/100)) -gt $MAXWAIT ]] ; then + BACKOFF_FACTOR=$(($MAXWAIT*100/$WAITTIME)) ; + fi ; + if ( $OUTLEVEL2 ) ; then + # UPTIME is used only for OUTLEVEL2 messages. + UPTIME=$(($UPTIME+$LASTPOLLTIME)) ; + fi ; + LASTUPTIME=$(($LASTUPTIME+$LASTPOLLTIME)) ; + if [ $LASTPOLLTIME -ne 0 ] ; then + $OUTLEVEL1 && ${OUTPUT1} "$DEVNAME spun up after $LASTDOWNTIME seconds." ; + fi + NUM_EQUALS=0 ; + $OUTLEVEL2 && ${OUTPUT2} "Waiting for $THISWAIT seconds of inactivity..." ; + PREVIOUS_STATS=`cat $STATSFILE` ; + while [[ $(($NUM_EQUALS*5)) -lt $THISWAIT ]]; do + sleep 5 ; + if ( $OUTLEVEL2 ) ; then + # UPTIME is used only for OUTLEVEL2 messages. + UPTIME=$(($UPTIME+5)) ; + fi ; + LASTUPTIME=$(($LASTUPTIME+5)) ; + NEXT_STATS=`cat $STATSFILE` ; + if [ "$PREVIOUS_STATS" != "$NEXT_STATS" ] ; then + NUM_EQUALS=0 ; + PREVIOUS_STATS="$NEXT_STATS" + $OUTLEVEL2 && ${OUTPUT2} "Restarting..." ; + else + NUM_EQUALS=$(($NUM_EQUALS+1)) ; + $OUTLEVEL2 && ${OUTPUT2} "Seconds of quiet: $(($NUM_EQUALS*5))" ; + fi + done + # We've just had $THISWAIT seconds of inactivity. The inactivity indicates + # that we're ready to go to sleep. Laptop mode will have synced all + # writes for us after the last read, so we don't have to explicitly + # sync. + if ( $NO_LAPTOP_MODE ) ; then + sync ; + fi ; + hdparm -q -y $DEVNAME ; + if ( $OUTLEVEL2 ) ; then + # SPINDOWNS is used only for OUTLEVEL2 messages. + SPINDOWNS=$(($SPINDOWNS+1)) ; + fi ; + $OUTLEVEL1 && ${OUTPUT1} "$DEVNAME spun down after $LASTUPTIME seconds (with $THISWAIT seconds of inactivity)." ; + LASTUPTIME=0 ; + LASTDOWNTIME=0 ; + else + if ( $OUTLEVEL2 ) ; then + # POLLSSPUNDOWN and SPINDOWNS are used only for OUTLEVEL2 messages. + POLLSSPUNDOWN=$(($POLLSSPUNDOWN+1)) ; + if [[ $SPINDOWNS -eq 0 ]] ; then + SPINDOWNS=1 ; + fi + fi ; + LASTDOWNTIME=$(($LASTDOWNTIME+$LASTPOLLTIME)) ; + BACKOFF_FACTOR=$(($BACKOFF_FACTOR*$BACKOFF_DECREASE_PCT/100)) ; + if [ $BACKOFF_FACTOR -lt 100 ] ; then + BACKOFF_FACTOR=100 ; + fi + fi ; + if ( $OUTLEVEL2 ) ; then + ${OUTPUT2} "spindowns: $SPINDOWNS, time up/down: $UPTIME/$(($POLLSSPUNDOWN*$POLLTIME)), backoff $BACKOFF_FACTOR, down for $LASTDOWNTIME (avg $(($POLLSSPUNDOWN*$POLLTIME/$SPINDOWNS)))." ; + fi ; + sleep $POLLTIME ; + LASTPOLLTIME=$POLLTIME ; +done diff --git a/smart_spindown@.service b/smart_spindown@.service new file mode 100644 index 0000000..909bf5c --- /dev/null +++ b/smart_spindown@.service @@ -0,0 +1,16 @@ +[Unit] +Description=Smart Spindown for %i +Wants=local-fs.target +After=syslog.target + +[Service] +Type=idle +WorkingDirectory=/etc/smart_spindown +StandardOutput=syslog +SyslogIdentifier=smart_spindown/output +SyslogLevel=err +ExecStart=/usr/local/bin/smart_spindown --conf=/etc/smart_spindown/%i.conf +Restart=on-failure + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3