Mac を触ったら重いバックグラウンド処理を中断し、しばらく触らなかったら再開するようにする
Mac OS X でバックグラウンド処理(主にネットワークを利用する処理を想定している)を行うのですが、人がキーボードやタッチパッドに触ったら、人間が行なう処理を優先するために、バックグラウンドの処理を一旦停止し、しばらくさわらなかったら処理を再開するスクリプトを書きました。
コードは Gist にあります。 ∥ Run processes while you do not touch your Mac.
動機
Mac に定期的に遠方から rsync でファイルをフェッチさせているのですが、いかんせんうちは田舎で ADSL しか来ておらず、さて作業をするぞとなると偉くネットが遅く、ああ、バックグラウンドで処理をさせていたんだっけ、ということによくなりましたので、UI に触っている間はバックグラウンドの処理を止めて、サクサクと作業ができるようにしたかったのです。
CPU 負荷の高いような処理でも使えるっちゃあ使えるかな? Mac でそんなワークロードがあるかどうかは分かりませんが。
で、何をしようとしたか(あるいは、何をしなかったのか)を忘れそうなのでメモしました。
実行方法
コマンド名は darwinbg
としています。このスクリプトを置いたのと同じ場所に darwinbg.d/
ディレクトリを作成しその下に実行可能ファイルを置いておけば、darwinbg を実行するたびに配下の実行ファイルを順次実行します。
ですので、適当な間隔で cron にでも仕掛けておくと良いかと思います。
UI に触れてはならない時間は idlethresh
に、その確認の間隔は checkinterval
に、ともに秒で指定します。
そこへ接続している間は処理を実行して欲しくない Wi-Fi の SSID は badssids
に、同じく処理を実行して欲しくないネットワークサービスは badnwsvcs
に、それぞれ配列で指定します。後者は、Bluetooth PAN でテザリングをしている間などが相当します。
環境変数 FORCE
に何かを入れて起動すれば、UI へのタッチがあっても中断しません。
要点としては、そんなところか。
#!/bin/bash
# -*- coding: utf-8 -*-
export PATH=/usr/local/bin:/usr/local/sbin/:/usr/bin/:/usr/sbin/:/bin/:/sbin/
## Avoid reentrance.
exec 9< $0
perl -mFcntl=:flock -e "open(LOCK,'<&=9');exit(!flock(LOCK,LOCK_EX|LOCK_NB))" || exit 0
## Exit for error or undefined.
set -eu
idlethresh=$((60 * 5))
checkinterval=10
# idlethresh=5
# checkinterval=5
## Do not start nor continue with these SSIDs.
badssids=("Too Slow", "Too Expensive")
## Do not start nor continue with these network services.
badnwsvcs=("Bluetooth PAN")
contains () {
local e
for e in "${@:2}"
do
test "$e" == "$1" && return 0
done
return 1
}
function badnetworkp() {
local ssid
local badnwsvc
ssid=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | sed -n -e "s/^ *SSID: //p")
if test -n "$ssid" && contains "$ssid" "${badssids[@]}"
then
echo "Bad Wi-Fi is active."
return 0
fi
for badnwsvc in "${badnwsvcs[@]}"
do
if test -n "$(/usr/sbin/networksetup -getinfo "Bluetooth PAN" | sed -ne s/"^IP address: //p")"
then
echo "Bad network service is active."
return 0
fi
done
return 1
}
if badnetworkp
then
echo "Network connection is bad one. Exiting."
exit 0
fi
: ${FORCE:=""}
## Get UI idle time in second.
function idlesec() {
test -n "$FORCE" && echo 86400 && return
echo $(($(ioreg -c IOHIDSystem | grep HIDIdleTime | head -n 1 | sed 's/.* = //') / 1000000000))
}
## Stop for a while if UI is recently used.
echo Waiting.
while :
do
test "$(idlesec)" -ge "$idlethresh" && break
sleep $checkinterval
done
## Get child process IDs.
function children() {
for pid in $(pgrep -P $1)
do
echo $pid
children $pid
done
}
## Send signal to all child processes.
function killtree() {
kill -s $1 $pid $(children $2) && :
}
## Absolute path of this script.
self="$(p="$0";while :;do t="$(readlink "$p")";cd "$(dirname "$p")";test -z "$t"&&break||p="$t";done;echo "$(pwd -P)/$(basename "$p")")"
pid=
function onexit() {
trap - SIGTERM # Reset to default handler
kill -- -$$ && : # Not option but process group
## command line - What is a stopped process in linux? - Super User http://superuser.com/questions/403200/what-is-a-stopped-process-in-linux
kill --CONT -- -$$ && :
}
# function onexit() {
# test -n "$pid" && killtree TERM "$pid" || :
# }
trap onexit SIGINT SIGTERM EXIT
## Run all the executables under $self.d/ sequently.
for file in $self.d/*
do
base=$(basename "$file")
test "${base:0:1}" = "_" && continue
echo "$base" | grep -q -e "~" -e "#" && continue
$file &
pid=$!
while :
do
if badnetworkp
then
echo Network connection has been changed to bad one. Exiting.
killtree TERM $pid
wait
exit 0
fi
stat="$(ps auxww $pid | tail -n +2 | awk '{print $8}' | sed -e 's/\(.\).*/\1/')"
test -z "$stat" && pid="" && break
case $stat in
R | S | D ) # Running
if test "$(idlesec)" -lt "$idlethresh"
then
killtree STOP $pid
echo "Stopped subprocess(es)."
fi
;;
T ) # Stopped
if test "$(idlesec)" -ge "$idlethresh"
then
killtree CONT $pid
echo "Restarted subprocess(es)."
fi
;;
esac
sleep $checkinterval
done
done
やらなかったこと&やめたこと
- 停止・再開を別プロセスから行う。
- 処理本体と、処理の停止・再開を行うプロセスを当初は別にしておいて双方を cron で起動していたのですが、よく考えたら必要が無かったののでやめました
- ただしこの方法であれば、本処理とその制御とを別コマンドにできるので、それはそれでメリットなんですよね。やや複雑になりますが。それについては、右記に書きました。 ∥ cron から起動されたジョブを、一時停止・再開する - Qiita
- 定期的なタイマー割り込みで、処理の停止・再開の要不要のチェックをする。
- USR1 をチマチマと発行してそのたびにチェックをする仕組みもやりかけたのですが、これまたよく考えたら不要だったのでやめました。
できれば
早くうちへも、高速な光ケーブルが来てくれるといいんですけどね。