寝付きの悪いMacBookにすやすや眠ってもらう

いつの頃からか、MacBookの寝付きが悪くなってしまった。スリープさせても1、2時間(ひどい時は数分)で勝手に目を覚ましてしまう...。スリープ解除後は、モニタのみ消灯して、OS環境は普通に稼働している。
もちろん、モニタを閉じれば確実にスリープすることは分かっている。しかし、現状の自分のMacBookは、サーバーを兼用した以下のような利用環境である。

利用環境

  • MacBook(2008 late)
  • OSX 10.6.8
  • システム環境設定 >> 省エネルギー >> 「ネットワークアクセスによってスリープを解除」にチェック入りの設定。
  • モニタは閉じずにスリープ。(アップルメニューからスリープ、あるいは電源ボタン-Sでスリープさせた状態)

つまり、Wake on Demand が有効な状態でMacBookを運用しているのだ。

  • Wake on Demand を活用すれば、有線・無線に関係なく、ネットーワークに接続されたスリープ中のMacBookに、いつでも接続して共有サービスを利用できるのだ。
  • 共有サービスとは、システム環境設定 >> 共有で表示される、ファイル共有・画面共有・Web共有...などである。
  • これでMacBookは、賢いサーバーになる。
    • 使わない時は必要最小限の電力で待機(スリープ)して、使う時にはすぐに反応してくれる。
  • おまけにMacBookにはバッテリーも装着されているので、高価なUPSを準備する必要もない。


...のはずなんだが、省エネルギー環境設定のとおりにスリープしてくれないのが今の悩み。これからの季節、数百万台のmacが一斉にちゃんとスリープしたら、多少の節電効果もありそうな気がする。確実にスリープする設定を求めて、試行錯誤してみた。

調査

  • まず、どのタイミングで勝手にスリープが解除されてしまうのか?調べてみた。
  • そうゆうことは、たいていログに記録されているはずである。
  • コンソール.appを起動して、スリープが解除された瞬間のログを探してみた。
  • ログリストを「すべてのメッセージ」にして、それらしきログを目で追ってみた。
  • しばらく遡ると、以下の記述が見つかった。
...(中略)...
11/07/05 7:32:22	kernel	System Sleep
11/07/05 7:45:27	kernel	Wake reason = UHC3
11/07/05 7:45:27	kernel	System Wake
...(中略)...
  • それにしても「すべてのメッセージ」だと、余分なログが多くて読みにくい...。
  • 送信元がkernelとなっているので、kernel.logを開いてみた。

スリープが解除される理由

  • kernel.logを開くと、もっと効率的にスリープとその解除を追うことができた。
...(中略)...
Apr 30 08:33:35 bebe-MacBook kernel[0]: System Sleep
Apr 30 08:33:35 bebe-MacBook kernel[0]: Wake reason = RTC
Apr 30 08:33:35 bebe-MacBook kernel[0]: RTC: maintenance alarm 2011/4/30 00:12:15, sleep 2011/4/29 23:33:34
Apr 30 09:12:15 bebe-MacBook kernel[0]: System Wake
Apr 30 09:12:15 bebe-MacBook kernel[0]: Previous Sleep Cause: 5
Apr 30 09:12:15 bebe-MacBook kernel[0]: AirPort: Link Down on en1. Reason 4 (Disassociated due to inactivity).
Apr 30 09:12:15 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 12
Apr 30 09:12:17 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 13
Apr 30 09:12:17 bebe-MacBook kernel[0]: Ethernet [AppleYukon2]: Link up on en0, 1-Gigabit, Full-duplex, Symmetric flow-control, Debug [796d,af08,0de1,0200,cde1,2800]
Apr 30 09:12:20 bebe-MacBook kernel[0]: Auth result for: 00:1f:5b:89:37:ed MAC AUTH succeeded
Apr 30 09:12:20 bebe-MacBook kernel[0]: AirPort: Link Up on en1
Apr 30 09:12:20 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 13
Apr 30 09:12:20 bebe-MacBook kernel[0]: AirPort: RSN handshake complete on en1
Apr 30 09:12:21 bebe-MacBook kernel[0]: IOPMrootDomain: idle revert
Apr 30 09:12:23 bebe-MacBook kernel[0]: R-state changed 2->0
Apr 30 09:12:23 bebe-MacBook kernel[0]: AFPSleepWakeHandler: waking up
Apr 30 09:12:25 bebe-MacBook kernel[0]: utun_ctl_connect: creating interface utun0
Apr 30 18:15:02 bebe-MacBook kernel[0]: AFPSleepWakeHandler: going to sleep
Apr 30 18:15:02 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 14
Apr 30 18:15:03: --- last message repeated 1 time ---
Apr 30 18:15:03 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 15
Apr 30 18:15:06: --- last message repeated 1 time ---
Apr 30 18:15:06 bebe-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 12
Apr 30 18:15:06 bebe-MacBook kernel[0]: PM mode before entering WoW is 1 and PM allowed state is 1 
Apr 30 18:15:06 bebe-MacBook kernel[0]: System Sleep
Apr 30 18:43:35 bebe-MacBook kernel[0]: Wake reason = RTC
Apr 30 18:43:35 bebe-MacBook kernel[0]: RTC: maintenance alarm 2011/4/30 09:43:35, sleep 2011/4/30 09:15:06
Apr 30 18:43:35 bebe-MacBook kernel[0]: System Wake
...(中略)...
  • 上記ログはその一部だが、地道に追跡すると、スリープが解除されるタイミングが見えてきた。
  • どうやら勝手にスリープが解除される時は必ず「Wake reason = RTC」のログがきっかけになっている。
  • 日本語訳すると「目を覚ます理由 = RTC」ということ。
  • RTCは何かというと、その下のログに書いてある。
  • 「RTC: maintenance alarm 2011/4/30 09:43:35, sleep 2011/4/30 09:15:06」
  • RTCとはメンテナンスアラームらしい。(時間が9時間ズレているのは、おそらくその部分だけ協定世界時 UTC で記述されているためと思われる)
  • 検索してみると、MacBookは2時間に1回(正確には1時間47、48分ごとだった)スリープを解除する仕様のようだ。
    • その理由は、Wake on Demand なスリープ中であっても、ネットワーク上にその存在を継続して通知するため。
  • 「Wake reason = RTC」で勝手にスリープが解除されるのは正常な仕様である。
  • そして仕様では、その後は速やかに再びスリープするはず。
  • しかし現状では、その後Sleepすることなく、Wake状態になってしまっている。

再びsleepさせるAppleScript

  • なぜ再びスリープしてくれないのか、その原因は分からない。
  • しかし、AppleScriptを使えば、強制的にスリープさせることは簡単にできる。
    • 1分ごとに「Wake reason = RTC」ログを監視して、
    • そのログが変化した時、強制的にスリープするようにしてみた。


property wake_log : ""

on run
set wake_log to my wake_reason_rtc() end run

on idle
set current_log to my wake_reason_rtc() if current_log"" and current_logwake_log then
set wake_log to current_log
reopen
end if
return 60
end idle

on reopen
my alert_going_to_sleep() my mac_sleep() end reopen

on wake_reason_rtc() do shell script "grep -e 'Wake reason = RTC' '/var/log/kernel.log'"
end wake_reason_rtc

on alert_going_to_sleep() my system_sound("Submarine.aiff") activate
display dialog "15秒後にスリープします。" with icon note giving up after 15
end alert_going_to_sleep

on mac_sleep() tell application "Finder" to sleep
--do shell script "pmset sleepnow >& /dev/null &"
end mac_sleep

on system_sound(fname) do shell script "(sleep 0; afplay /System/Library/Sounds/" & fname & ") >& /dev/null &"
end system_sound

  • 上記スクリプトを以下のフォーマットで保存した。
    • ファイルフォーマット:アプリケーション
    • オプション:「実行後、自動的に終了しない」チェック入り
    • ファイル名:sleep_keeper.app

それでも寝不足気味

  • 上記スクリプトを実行しておけば、「Wake reason = RTC」でWakeした時には、1分以内に強制的にスリープしてくれるはずである。
  • ...そのはずなのだが、何だかおかしい。うまく機能してくれる時もあるのに、ちゃんとスリープしない時もある。
  • さらに困ったことに、ちゃんとスリープしない時には、有効にしてあるはずの共有サービスがすべて使えなくなってしまう。困った...。
    • MacBookの前に座って、手動でスリープ&解除すれば、共有サービスは復活する。
    • しかし、その都度MacBookのあるサーバー部屋まで足を運ぶのは、非常に不便...。

原因追求

  • 問題ありそうなプロセスを強制終了させたり、条件を変えてスリープさせる中で少しずつ分かってきた。
  • どうも、AppleScriptからスリープさせた時に問題が発生しているようなのだ。
  • しかも、目の前で実験するとうまくいくのに、見ていない所では異常な動作。(何だか量子論的な謎の振る舞い)
  • シンプルな実験スクリプトを作って、実行してみた。
  • 省エネルギー環境設定は、ディスプレイのスリープは1分、コンピュータのスリープは3分に設定しておいた。


delay 70
tell application "Finder" to sleep

  • 60秒後にディスプレイがスリープして、70秒後にコンピュータがスリープするはずなのだが、
  • 実行してみると、70秒後のコンピュータのスリープは実現されない...。
  • 一方、delay 60にして再度実行すると、こちらはちゃんとコンピュータがスリープする。


delay 60
tell application "Finder" to sleep


つまり、ディスプレイがスリープした状態では、上記AppleScriptからのスリープが正常に機能しないのだ!(自分の環境では)

確実にスリープさせるコード

  • 実験の結果、ディスプレイがスリープ中だと、以下のスクリプトはことごとくスリープに失敗した。


tell application "Finder" to sleep
tell application "System Events" to sleep
tell application "loginwindow" to «event aevtslep»

  • そして、唯一どんな条件でも確実にスリープさせる書き方も一つ見つかった。


do shell script "pmset sleepnow"

  • 早速、現在活用しているAppleScriptのスリープさせる部分のコードを「pmset sleepnow」方式にすべて書き直した。


これで、勝手に目覚めてしまうスリープ問題はひとまず解決。
共有サービスが使えなくなってしまう症状も発生しなくなった。

自動スリープ

そもそも、もう一つ重要なスリープ問題があった。

  • 省エネルギー環境設定で、コンピュータのスリープを1分にしてみる。
  • 何も操作しないで1分間じっと我慢すると、ディスプレイはスリープする。
  • しかし、コンピュータはいつまで経ってもスリープしないのだ...。
  • もちろん、映像や音楽を再生していない状態である。
  • 外付けハードディスクに最新のOSX 10.6.8をインストールしてみた。
  • サードパーティのソフトウェアは一切インストールなしの純粋な状態で起動してみた。
  • にもかかわらず、自動スリープしてくれない。終わっとる...。(外付けHDだから? )
  • またしてもAppleScriptで自動スリープさせるかと考えたが、何も操作をしない状態をAppleScriptで検出するのって、結構難しそうだ。
  • ところで、この自動スリープしない問題は、多くのユーザーの悩みらしく、検索してみると多くの話題がヒットする。
  • 世界は広く、達人はどこかに居いる。この問題を解決する素晴らしいアプリケーションが既に開発されていた。

PleaseSleep

  • 起動しておくだけで、省エネルギー環境設定のとおりに、自動スリープするようになる。
  • Dcokには表示されず、メニューバーに表示されるバックグラウンドなアプリケーション。


PleaseSleep、優秀過ぎる!

  • 先に作ったsleep_keeperAppleScript)の代替にもなるかもしれない。(自動スリープの間隔を短くしておけば良いのだ)


以上で、MacBookは放っておいても勝手にすやすや眠るようになったのであった。