PR

AppNapライクにSafari,Firefoxをコントロール(cputhrottle & applescript)v3.0

Firefox, Safariが未使用時、CPU使用率を下げて発熱を抑えるAppNapもどき

スクリプトの改善版です。

AppNapライクにWebProcessをコントロール(cputhrottle & applescript)
AppNapライクにWebProcessをコントロール(cputhrottle & applescript)v2.0

v2.0 不定期にエラーが発生しています。

sh: -c: line 0: syntax error near unexpected token '('
sh: -c: line 0: 'logger -t appnap-browser -i 
errorMessage: /bin/sh: line 0: kill: (1553) - No such process, errorNumber: 1'

エラーの内容からすると、
loggerコマンドでログを出力する際に
指定したログメッセージに誤りがあるようです。

(とかあるとエスケープされずにそのまま出力されてエラーになると予想できます。

quoted fromでログメッセージをエスケープ処理するように修正します。
do shell script “logger -t appnap-browser -i ” & quoted form of logmsg

これ以外に、いくつか事象・現象が発生していました。

  • /tmp/にコマンドを置いていましたが、しばらくすると/tmp/のクリア処理が動作し、消去されて起動できなくなる事象
  • sleep復帰後、たまにcputhrottleが落ちている現象

コマンド位置の変更と、cputhrottleの存在確認の修正を合わせて行いました。

appnap-webbrowser.app(applescript) v3.0

前提

  • cputhrottleコマンドがインストールされている。
  • スクリプトを実行するユーザは管理者権限を持っている

cputhrottleは、こちら(AppNapライクにWebProcessをコントロール(cputhrottle & applescript))を参考にしてください。

ソース

以下ソースをAppleScriptエディタにコピペします。
コピペ後、以下設定を書き換えてください。

  • myPassword to “– 管理者パスワードに置き換えてください –” の行を修正してください。
  • CPU_SLOW_COMMAND to “/path/to/unix”の行を修正してください。
    cputhrottleコマンドをご自身の環境に合わせて修正してください。
    ex) cputhrottleコマンドが/Users/Username/command/cputhrottleにある場合
    set CPU_SLOW_COMMAND to “/Users/Username/command/cputhrottle”
    と指定します。

ファイルフォーマット:アプリケーション、オプションとして「ハンドラの実行時に終了しない」を選んで、書き出します。
(ここではファイル名:appnap-browser.appを想定しています)

global CPU_SLOW_PERCENTAGE
global CPU_SLOW_COMMAND
global myProcessDataList
global myPassword
global process_monitor_interval
global process_monitor_counter

set myPassword to "-- 管理者パスワードに置き換えてください --" -- <- 管理者のパスワード
set CPU_SLOW_PERCENTAGE to "5" -- <- フォーカスがない場合:プロセスのCPU使用率をpercentで指定
-- cputhrottleコマンドの場所を指定
set CPU_SLOW_COMMAND to "/path/to/unix/cputhrottle"
-- cputhrottleコマンドの生存確認間隔を指定(秒)
set process_monitor_interval to 60 -- On idle が60回呼び出されたら

set myProcessDataList to {}
set process_monitor_counter to 0
-- ===========================
-- ProcessData
-- クラス
script ProcessData
	property pid : -1
	property procName : ""
	
	on new(thePid, theProcName)
		copy me to theData
		set_pid(thePid) of theData
		set_procName(theProcName) of theData
		theData
	end new
	on set_pid(value)
		set pid to value
	end set_pid
	on set_procName(value)
		set procName to value
	end set_procName
end script

-- ===========================================
-- FindProcessData
--  theList配列中のtheProcNameを検索する
-- 入力:	theList ProcessDataの配列
--		theProcName 検索するプロセス名を指定
-- 出力:	retList 検索した結果(ProcessData配列)
-- 返り値: 検索した結果(ProcessData配列)
on FindProcessDataList(theList, theProcName, retList)
	repeat with v in theList
		if procName of v is equal to theProcName then
			set end of retList to v
		end if
	end repeat
	return retList
end FindProcessDataList

-- ===========================================
-- DeleteProcessDataList
--  theList配列中のtheProcNameを配列から削除する
-- 入力:	theList ProcessDataの配列
--		theProcName 削除するプロセス名を指定
-- 出力:	retList 削除されなかったデータ配列(ProcessData配列)
-- 返り値: 削除されなかったデータ配列(ProcessData配列)
on DeleteProcessDataList(theList, theProcName)
	set retList to {}
	repeat with v in theList
		if procName of v is not equal to theProcName then
			set end of retList to v
		end if
	end repeat
	return retList
end DeleteProcessDataList

-- ===========================================
-- StartBackgroundCpuControl
--  プロセスの使用率を下げる
-- 入力:	theProcName 使用率を下げるプロセス名を指定
-- 出力:	theControledList コントロール中のデータ配列(ProcessData配列)
-- 返り値: なし
on StartBackgroundCpuControl(theProcName, theControledList)
	tell application "System Events"
		set pList to (unix id of every process whose name contains theProcName)
		if pList is not equal to {} then
			-- syslog("StartBackgroundCpuControl  " & theProcName & "") of me
			repeat with unixID in pList
				do shell script CPU_SLOW_COMMAND & " " & (unixID as string) & " " & CPU_SLOW_PERCENTAGE & " >/dev/null 2>&1 & echo $!" password myPassword with administrator privileges
				set pid to result
				syslog("start " & theProcName & " cputhrottle  " & (unixID as string) & " " & CPU_SLOW_PERCENTAGE & ", PID=" & pid) of me
				set end of theControledList to new(pid, theProcName) of ProcessData
			end repeat
		end if
	end tell
end StartBackgroundCpuControl

-- ===========================================
-- StopBackgroundCpuControl
--  プロセスの使用率をもとに戻す
-- 入力:	theControledList もとに戻すデータ配列(ProcessData配列)
-- 出力:	なし
-- 返り値: なし
on StopBackgroundCpuControl(theControledList)
	-- syslog("StopBackgroundCpuControl  targetCount=" & (length of theControledList as string) & "") of me
	repeat with v in theControledList
		try
			if IsExistsProcess(pid of v) of me is equal to true then
				do shell script "kill -INT " & (pid of v as string) password myPassword with administrator privileges
				syslog("stop " & (procName of v) & " cputhrottle , PID=" & (pid of v as string)) of me
			end if
		on error errorMessage number errorNumber
			syslog("StopCpuControl errorMessage: " & errorMessage & ", errorNumber: " & errorNumber) of me
		end try
	end repeat
end StopBackgroundCpuControl

-- ===========================================
-- IsExistsProcess
--  プロセスの存在確認
-- 入力:	thePid プロセスID
-- 出力:	なし
-- 返り値: true 存在 false 存在しない
on IsExistsProcess(thePid)
	try
		set the_result to do shell script "ps -p " & (thePid as string) & " | awk '{print $1}' | grep " & (thePid as string)
		if the_result is equal to (thePid as string) then
			return true
		else
			return false
		end if
	on error errorMessage number errorNumber
		return false
	end try
end IsExistsProcess

-- ===========================================
-- CheckingBackgroundCpuProcess
--  プロセスの存在確認
-- 入力:	theList 存在を確認するデータ配列(ProcessData配列)
-- 出力:	なし
-- 返り値: 存在を確認したデータ配列(ProcessData配列)
on CheckingBackgroundCpuProcess(theList)
	set retList to {}
	repeat with v in theList
		if IsExistsProcess(pid of v) of me is equal to false then
			syslog("detect CPUSlowControlProcess down. pid=" & (pid of v as string) & " origin=" & (procName of v)) of me
		else
			set end of retList to v
		end if
	end repeat
	return retList
end CheckingBackgroundCpuProcess

on idle
	try
		tell application "System Events"
			set activeProcs to every process whose frontmost is true
			set activeProc to item 1 of activeProcs
			tell activeProc
				set activeProcName to name
			end tell
		end tell
		if process_monitor_counter = process_monitor_interval then
			set process_monitor_counter to 0
			set myProcessDataList to CheckingBackgroundCpuProcess(myProcessDataList)
		else
			set process_monitor_counter to process_monitor_counter + 1
		end if
		
		if activeProcName is equal to "Safari" then
			set targetProcess to {}
			set targetProcess to FindProcessDataList(myProcessDataList, "WebProcess", targetProcess)
			if length of targetProcess is not equal to 0 then
				StopBackgroundCpuControl(targetProcess)
				set myProcessDataList to DeleteProcessDataList(myProcessDataList, "WebProcess")
			end if
		else if activeProcName is equal to "Firefox" then
			set targetProcess to {}
			set targetProcess to FindProcessDataList(myProcessDataList, "Firefox", targetProcess)
			if length of targetProcess is not equal to 0 then
				StopBackgroundCpuControl(targetProcess)
				set myProcessDataList to DeleteProcessDataList(myProcessDataList, "Firefox")
			end if
		else
			set targetProcess to {}
			set targetProcess to FindProcessDataList(myProcessDataList, "WebProcess", targetProcess)
			if length of targetProcess is equal to 0 then
				-- 制御中は、再実行しない
				StartBackgroundCpuControl("WebProcess", myProcessDataList)
			end if
			set targetProcess to {}
			set targetProcess to FindProcessDataList(myProcessDataList, "Firefox", targetProcess)
			if length of targetProcess is equal to 0 then
				-- 制御中は、再実行しない
				StartBackgroundCpuControl("Firefox", myProcessDataList)
			end if
		end if
		return 1
	on error errorMessage number errorNumber
		syslog("onIdle errorMessage: " & errorMessage & ", errorNumber: " & errorNumber) of me
		error
	end try
	
end idle

on quit
	set targetProcess to {}
	set targetProcess to FindProcessDataList(myProcessDataList, "WebProcess", targetProcess)
	set targetProcess to FindProcessDataList(myProcessDataList, "Firefox", targetProcess)
	if length of targetProcess is not equal to 0 then
		StopBackgroundCpuControl(targetProcess)
		set myProcessDataList to {}
	end if
	syslog("appnap-browser closed.") of me
	continue quit
end quit

on syslog(logmsg)
	do shell script "logger -t appnap-browser -i " & quoted form of logmsg
end syslog

終了方法

起動するとdockにappnap-browserアイコンが存在しています。

appnap-browserアイコンを選択後、
メニューから終了を選んでください。
(appnap-browser -> appnap-browserを終了)

タイトルとURLをコピーしました