Inotify APC Smarty Recompiler
これは何?
PHPの高速化をするにはAPCを使ったりSmartyのオプションを色々変えたりということをすると高速化できます。
特にファイル変更のチェックをはずすと高速化出来ますが、その代わりPHPの利点であるサーバを動かしながら変更を加えるということが
出来なくなってしまいます。
ということで、ファイルの変更チェックオプションは生かしておいて運用するというのもありなのですが、それでは高速化できないよ。
でも、動かしながら変えられないよなんとかならないのかー???
ってことで、ファイル監視は別な人がやるよ。っていうのがこの、Inotify APC Smartyリコンパイラなわけです。
Inotify って何?
Linuxでファイルやディレクトリが変更された場合に監視対象としているもののみの通知をしてくれる仕組みです。
PHPでもInotifyを使うライブラリがあるのでPHPでファイル監視プログラムを作ることが出来ます。
問題点
apcのリコンパイルはapacheのphp上で動かさないといけません。コマンドラインからは動かしてもエラーになってしまいます。
空間が違うからみたい。
対策方法はapache上で常駐してしまうか、リコンパイル部分だけapache上で動く簡単なphpを置くことです。
で、どうやって使うんだ?
APCをインストールします。
参考: http://www.ideaxidea.com/archives/2009/01/php_apc.html
Smartyをインストールします。
参考: http://www.phpbook.jp/smarty/install/
Inotifyをインストールします。
参考: http://ameblo.jp/itboy/entry-10173242939.html
この辺は適当にググッただけなので、色々調べてください。
APCのファイルチェックをはずします。
apc.stat = Off
$smarty = new Smarty(); $smarty->compile_check = false;
設定パターン2
/etc/init.d/iasrecompd
等を作っていい感じに。
/recompile.phpがアパッチ上でリコンパイルをするようにします。
あとは、ファイルを書き換えたりしてみればうまくいったり行かなかったりします。
うまく行かない場合はコマンドライン上でinotifyapcsmartyrecompiler.phpを実行してみて問題をつぶしましょう。
ソースそのままだと、smartyが動かないと思うので適当にincludeかrequireしてください。
excludesには監視対象から省く正規表現を書くことが出来ます。
あと、ほかに監視して何かしたい場合はgetPatternとexecute($file,$m)メソッドを持ったクラスを作って登録すれば動くので
いろいろ拡張できます。
ファイルをアップしたら、変換するプログラムとかアンなことやこんなこともと夢が広がります。
<?php
class Inotify {
var $files;
var $excludes;
var $actions;
function __construct($params) {
$this->files = $params['files'];
$this->excludes = $params['excludes'];
$this->actions = $params['actions'];
$this->watches = array();
$this->settings = array();
}
function run() {
// 自動フラッシュをオン
ob_implicit_flush(1);
if (!extension_loaded('inotify')) {
fprintf(STDERR, "Inotify extension not loaded !\n");
exit(1);
}
$this->inotify = inotify_init();
if ($this->inotify === false) {
fprintf(STDERR, "Failed to obtain an inotify instance\n");
return 1;
}
foreach($this->files as $file) {
// 再帰的ディレクトリ登録
$rc = $this->addWatch($file);
if ($rc != 0) return $rc;
}
$rc = $this->main_loop();
// May not happen
inotify_rm_watch($this->inotify, $watch);
fclose($this->inotify);
return $rc;
}
function addWatch($file) {
foreach($this->excludes as $exclude) {
if (strpos($file, $exclude) !== false) return 0;
}
if (isset($this->settings[$file])) return 0;
echo "add ".$file;
$watch = inotify_add_watch($this->inotify, $file, IN_MODIFY|
IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE |
IN_DELETE | IN_DELETE_SELF | IN_MOVE_FROM|
IN_MOVE_TO | IN_MOVE | IN_DONT_FOLLOW | IN_ONLYDIR);
$this->watches[$watch]=$file;
$this->settings[$file]=true;
echo " $watch\n";
if ($watch === false) {
fprintf(STDERR, "Failed to watch file '%s'", $file);
return 1;
}
if (!is_dir($file)) {
break;
}
$dir = opendir($file);
while($f = readdir($dir)) {
if ($f == ".." || $f == ".") continue;
$fname = $file.$f."/";
if (is_dir($fname)) {
$this->settings[] = $fname;
$rc = $this->addWatch($fname);
if ($rc != 0) return $rc;
}
}
closedir($dir);
return 0;
}
function main_loop() {
// 変化のあったファイルを格納する配列
$actionFiles = array();
// イベントループ
while (($events = inotify_read($this->inotify)) !== false) {
// イベントをすべて処理
foreach($events as $event) {
// ファイル名を取得
$file = $this->watches[$event['wd']] . $event['name'];
// ファイルを省く
foreach($this->excludes as $exclude) {
if (strpos($file, $exclude) !== false) continue 2;
}
if (is_dir($file)) {
// ディレクトリ追加
$this->addWatch($file."/");
} else {
// アクションファイルに追加
$actionFiles[$file] = $file;
}
}
// 300msほど待ち
usleep (300000);
if (inotify_queue_len($this->inotify)>0) {
// キューにまだデータがある場合はここまで
continue;
}
// 溜め込んだファイルのアクションを実行
foreach($actionFiles as $file) {
// 登録してあるアクションでループ
foreach($this->actions as $action) {
// アクションにマッチ
$m = array();
if (preg_match($action->getPattern(), $file, $m)) {
// アクション実行
$action->execute($file, $m);
}
}
}
// アクションファイルの初期化
$actionFiles = array();
}
return 0;
}
}
/**
* phpのapcのリコンパイラ
*/
class PHPAPCReCompileAction {
/**
* phpのファイルにマッチする正規表現
*/
function getPattern() {
return "/\\.php$/";
}
/**
* ファイルをコンパイルして、失敗したらキャッシュをすべて消す
*/
function execute($file, $m) {
/* echo "compile $file\n";
if(apc_compile_file($file)===false){
echo "compile $file error\n";
apc_clear_cache();
// 出来れば、コンパイルしたファイルだけ削除したいけど出来ない。。。
}*/
// リコンパイルだけはapacheでやる
file_get_contents("http://localhost/recompile.php?passfraze=dhfsryewqnvdkl70hbjly81djfs4y8319fhjdks1eup&filename=".$file);
}
}
/**
* Smartyのテンプレートアクション
*/
class SmartyTemplateAction {
/**
* コンストラクタ
*/
function __construct($smarty) {
$this->smarty = $smarty;
}
/**
* Smartyのテンプレートにマッチする正規表現
*/
function getPattern() {
$htmlt = "/". str_replace("/","\\/",$this->smarty->template_dir)."\\/(.+\\.html|.+\\.tpl)$/";
return $htmlt;
}
/**
* コンパイルされたファイルを削除
*/
function execute($file, $m) {
echo "remove {$m[1]}\n";
$this->smarty->clear_compiled_tpl($m[1]);
}
}
$smarty = new Smarty();
$smarty->template_dir = APP_DIR.'/templates/template';
$smarty->compile_dir = APP_DIR.'/templates/templates_c';
$inotify = new Inotify(array(
"files" => array(
dirname(dirname(__FILE__))."/html/",
dirname(dirname(__FILE__))."/php/",
),
"excludes" => array(
"/logs/",
),
"actions"=> array(
new PHPAPCReCompileAction(),
new SmartyTemplateAction($smarty),
),
));
$inotify->run();
?>recompile.php
<?php if($_REQUEST["passfraze"]!="dhfsryewqnvdkl70hbjly81djfs4y8319fhjdks1eup") exit(0); if($_REQUEST["filename"]) { if (apc_compile_file($_REQUEST["filename"])===false){ apc_clear_cache(); } }