Proong

GroongaのPHPライブラリを作っています。
https://github.com/Yujiro3/proonga

インストール
Groongaライブラリ

$ sudo aptitude install -y libgroonga0 libgroonga-dev
Groonga v4.0.7で追加されたC-APIを利用しています。
最新版のライブラリを利用してください。

proongaのインストール

$ git clone https://github.com/Yujiro3/proonga.git
$ cd ./proonga
$ phpize
$ ./configure
$ make
$ sudo -s
# make install
# cd /etc/php5/mods-available
# echo extension=groonga.so > proonga.ini
# cd /etc/php5/conf.d
# ln -s ../mods-available/proonga.ini ./30-proonga.ini

php_json_encode_exを利用してます。
php5-jsonを有効にしてください。
CentOSなどでは、php-commonに含まれているので
proonga.iniの読み込みタイミングに気をつけてください。

phpの下位互換用の分岐を書いていません。
php5.3.x以上で動くと思いますが、PHP 5.5.18で開発しています。

クラス一覧
Groongaクラス
GCommandクラス
GTableクラス
GColumnクラス
GLoadクラス
GDeleteクラス
GSelectクラス

サンプル
micro_blog.php https://gist.github.com/Yujiro3/1ce4908b191cfaf77b5d
コード

/* select --table Users --match_columns name,location_str,description --query "New York" --output_columns _key,name */
$result = $gdb->table('Users')
    ->select()
    ->matchColumns('name,location_str,description')
    ->query('"New York"')
    ->outputColumns('_key,name')
    ->exec(true);

echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_BIGINT_AS_STRING);

結果

[
    [
        [
            1
        ],
        [
            [
                "_key",
                "ShortText"
            ],
            [
                "name",
                "ShortText"
            ]
        ],
        [
            "bob",
            "Bob"
        ]
    ]
]

Groongaの組み込みコマンドを実行するクラス群です。
極力ソースを書かないように心がけたのでバグも少ないはずです。

CentOSでzend_call_method_with_*_params()関数がうまく動かなかったので
MongoDBのバインディングとかが使っている方法を試してみました。
とりあえず問題ないようです。

Groongaの組み込みコマンドが楽ちん過ぎて普通書き方に戻れない。。。

お役立ちリンク
Groonga Cライブラリを使う上で知っておいてほしいこと

Groonga関連のドキュメント

Groonga組み込みコマンドの利用

Websocketなどの持続接続型サーバ

Exchangerとは
Websocketなど接続が持続する処理にFasctCGIを利用して処理できるサーバです。

[github:exchanger]
exchanger

Exchangerフロー
001
接続処理はExchangerサーバが処理し、アプリケーション処理はFastCGI(php5-fpm)で行います。

002


チャットアプリ

・8888ポートの待受をexchangerサーバ
・アプリケーション処理をphp5-fpm
・ブラウザ側処理はjavascript


感想
私は、PHPerと思います。
ExchangerはC++で作成しました。
C言語をまともに使ったのも5年ぶりぐらい、C++はVC以来なので10年以上ぶり
C++の書き方が合っているのか、。。。ではあります。

fluentdをPHPでも作ってしまえと思いphp5-tailを作ったときにモジュールを調べました。
その時にlibeventにヒットしました。
phpからもlibeventは簡単に使えて、websocketサーバなども作ってみました。
その時に、phpをサーバとして使ってよいものか?
下手にCで作るよりはphpに任せたほうがまともに動くだろうとは思います。

私は、PHPが好きなんです。
リアルタイム処理をnode.jsに全部持っていかれるのが嫌なのです。
PHPでテンプレートエンジンを使うバカが大っ嫌いなのです。

nginxなどから直接、FastCGIでwebsocketが使えないのかな?
調べた限り納得できるものがなかったので作ってみたという感じです。

幸いlibeventがあまりにも手軽なため、C++で作ることができました。

Exchangerは、FastCGIをアプリケーション処理に使います。
ExchangerとFastCGIの共有リソースとしてRedisを使います。

自分の好きな言語でリアルタイム処理のアプリケーションが作れるようになると思います。


ToDo
・SSL/TLS処理の実装(テストなどがまだ)
・セキュリティ強化
・再接続関連
・サンプルアプリ(node.jsから移植)

./configure実行時のエラー

コンパイル時のライブラリ認識エラー

phpize
./configure

configure: error: wrong "***" lib version or lib not found

ライブラリ一覧に存在するか確認

$ ldconfig -p | grep "***"

インストールしたはずのライブラリ名を検索してなかったら
ライブラリリストを更新させます。

ライブラリリストの更新

sudo ldconfig
ldconfig -p |grep libevent
        libevent_pthreads-2.0.so.5 (libc6,x86-64) => /usr/local/lib/libevent_pthreads-2.0.so.5
        libevent_pthreads-2.0.so.5 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent_pthreads-2.0.so.5
        libevent_openssl-2.0.so.5 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent_openssl-2.0.so.5
        libevent_extra-2.0.so.5 (libc6,x86-64) => /usr/local/lib/libevent_extra-2.0.so.5
        libevent_extra-2.0.so.5 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent_extra-2.0.so.5
        libevent_core-2.0.so.5 (libc6,x86-64) => /usr/local/lib/libevent_core-2.0.so.5
        libevent_core-2.0.so.5 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent_core-2.0.so.5
        libevent-2.0.so.5 (libc6,x86-64) => /usr/local/lib/libevent-2.0.so.5
        libevent-2.0.so.5 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent-2.0.so.5

Websocketなどの持続接続型サーバ

Exchangerとは
Websocketなど接続が持続する処理にFasctCGIを利用して処理できるサーバです。

[github:exchanger]
exchanger

Exchangerフロー
001
接続処理はExchangerサーバが処理し、アプリケーション処理はFastCGI(php5-fpm)で行います。

002


チャットアプリ

・8888ポートの待受をexchangerサーバ
・アプリケーション処理をphp5-fpm
・ブラウザ側処理はjavascript


感想
私は、PHPerと思います。
ExchangerはC++で作成しました。
C言語をまともに使ったのも5年ぶりぐらい、C++はVC以来なので10年以上ぶり
C++の書き方が合っているのか、。。。ではあります。

fluentdをPHPでも作ってしまえと思いphp5-tailを作ったときにモジュールを調べました。
その時にlibeventにヒットしました。
phpからもlibeventは簡単に使えて、websocketサーバなども作ってみました。
その時に、phpをサーバとして使ってよいものか?
下手にCで作るよりはphpに任せたほうがまともに動くだろうとは思います。

私は、PHPが好きなんです。
リアルタイム処理をnode.jsに全部持っていかれるのが嫌なのです。
PHPでテンプレートエンジンを使うバカが大っ嫌いなのです。

nginxなどから直接、FastCGIでwebsocketが使えないのかな?
調べた限り納得できるものがなかったので作ってみたという感じです。

幸いlibeventがあまりにも手軽なため、C++で作ることができました。

Exchangerは、FastCGIをアプリケーション処理に使います。
ExchangerとFastCGIの共有リソースとしてRedisを使います。

自分の好きな言語でリアルタイム処理のアプリケーションが作れるようになると思います。


ToDo
・SSL/TLS処理の実装(テストなどがまだ)
・セキュリティ強化
・再接続関連
・サンプルアプリ(node.jsから移植)

ベンチマーク

abによる比較
簡易HTTPサーバを作成してベンチマークを行いました。

ベンチ対象のサーバ
 ・libevent1…C言語
 ・libevent2…C言語
 ・libevent2…PHP言語
 ・node.js …JavaScript
 ・nginx
 ・shortfin


ベンチマークリスト

Requests/s min median max
libevent1 5555.34 47 87 125
libevent2 5207.06 51 91 113
PHP 5013.12 53 89 270
JavaScript 2042.88 2 47 912
nginx 4612.45 50 81 137
shortfin 5240.77 12 19 40


abのインストール

$ sudo aptitude install apache2-utils

[Apache Benchを使った負荷テストのやり方]


libevent1

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        87 bytes

Concurrency Level:      500
Time taken for tests:   0.900 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      755000 bytes
HTML transferred:       435000 bytes
Requests per second:    5555.34 [#/sec] (mean)
Time per request:       90.004 [ms] (mean)
Time per request:       0.180 [ms] (mean, across all concurrent requests)
Transfer rate:          819.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       31   41   2.8     42      48
Processing:     7   46   3.1     46      84
Waiting:        4   27  11.0     27      51
Total:         47   87   4.1     87     125

Percentage of the requests served within a certain time (ms)
  50%     87
  66%     89
  75%     89
  80%     90
  90%     91
  95%     92
  98%     92
  99%     92
 100%    125 (longest request)

httpd1.c
[c]
#include <stdio.h>
#include <stdlib.h>

#include <event.h>
#include <evhttp.h>

/**
* コールバック
*/
static void send_document_cb(struct evhttp_request *req, void *arg) {
struct evbuffer *evb = NULL;

evb = evbuffer_new();
if (!evb) {
fprintf(stderr, "evbufferの生成に失敗しました。\n");
exit(1);
}

evbuffer_add_printf(
evb,
"<html>\n"
"<head>\n"
"<title>libevent</title>\n"
"</head>\n"
"<body>\n"
"libeventテスト\n"
"</body>\n"
"</html>\n"
);

evhttp_send_reply(req, 200, "OK", evb);
evbuffer_free(evb);
}

/**
* メイン
*/
int main() {
struct evhttp *http;

unsigned short port = 9090;

event_init();

/* アドレス・ポート指定 */
http = evhttp_start("0.0.0.0", port);
if (!http) {
fprintf(stderr, "evhttpの生成に失敗しました。\n");
return 1;
}

// evhttp_set_cb(http, "/dump", dump_request_cb, NULL);
/* デフォルトアクセス */
evhttp_set_gencb(http, send_document_cb, NULL);

event_dispatch();
evhttp_free(http);

return 0;
}
[/c]

コンパイル

gcc -L/usr/lib -levent -o httpd1 httpd1.c

libevent2

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        87 bytes

Concurrency Level:      500
Time taken for tests:   0.960 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      755000 bytes
HTML transferred:       435000 bytes
Requests per second:    5207.06 [#/sec] (mean)
Time per request:       96.023 [ms] (mean)
Time per request:       0.192 [ms] (mean, across all concurrent requests)
Transfer rate:          767.84 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       27   42   4.6     43      51
Processing:     7   50  10.6     48      76
Waiting:        5   38   8.9     37      58
Total:         51   92   9.6     91     113

Percentage of the requests served within a certain time (ms)
  50%     91
  66%     94
  75%     99
  80%    102
  90%    104
  95%    106
  98%    109
  99%    110
 100%    113 (longest request)

httpd2.c
[c]
#include <stdio.h>
#include <stdlib.h>

#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>

/**
* コールバック
*/
static void send_document_cb(struct evhttp_request *req, void *arg) {
struct evbuffer *evb = NULL;

evb = evbuffer_new();
if (!evb) {
fprintf(stderr, "evbufferの生成に失敗しました。\n");
exit(1);
}

evbuffer_add_printf(
evb,
"<html>\n"
"<head>\n"
"<title>libevent</title>\n"
"</head>\n"
"<body>\n"
"libeventテスト\n"
"</body>\n"
"</html>\n"
);

evhttp_send_reply(req, 200, "OK", evb);
evbuffer_free(evb);
}

/**
* メイン
*/
int main() {
struct event_base *base;
struct evhttp *http;
struct evhttp_bound_socket *handle;

unsigned short port = 9090;

base = event_base_new();
if (!base) {
fprintf(stderr, "event_baseの生成に失敗しました。\n");
return 1;
}

/* Create a new evhttp object to handle requests. */
http = evhttp_new(base);
if (!http) {
fprintf(stderr, "evhttpの生成に失敗しました。\n");
return 1;
}

// evhttp_set_cb(http, "/dump", dump_request_cb, NULL);
/* デフォルトアクセス */
evhttp_set_gencb(http, send_document_cb, NULL);

/* アドレス・ポート指定 */
handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port);
if (!handle) {
fprintf(stderr, "%d. ポートを確保出来ませんでした。\n", (int)port);
return 1;
}

event_base_dispatch(base);
event_base_free(base);

return 0;
}
[/c]

コンパイル

gcc -L/usr/local/lib -levent -o httpd2 httpd2.c

libevent2 + PHP

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        87 bytes

Concurrency Level:      500
Time taken for tests:   0.997 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      755000 bytes
HTML transferred:       435000 bytes
Requests per second:    5013.12 [#/sec] (mean)
Time per request:       99.738 [ms] (mean)
Time per request:       0.199 [ms] (mean, across all concurrent requests)
Transfer rate:          739.24 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        8   36   7.3     36      52
Processing:    15   53  13.2     53     262
Waiting:       10   41  12.2     41     243
Total:         53   89  10.4     89     270

Percentage of the requests served within a certain time (ms)
  50%     89
  66%     93
  75%     95
  80%     96
  90%     99
  95%    104
  98%    108
  99%    110
 100%    270 (longest request)

httpd.php
[php]
<?php
function _http_default($req) {
$buff = $req->getOutputBuffer();
$buff->add(
"<html>\n".
"<head>\n".
"<title>libevent</title>\n".
"</head>\n".
"<body>\n".
"libeventテスト\n".
"</body>\n".
"</html>\n"
);
$req->sendReply(200, "OK");
}

$base = new EventBase();
$http = new EventHttp($base);
$http->setAllowedMethods(EventHttpRequest::CMD_GET | EventHttpRequest::CMD_POST);

//$http->setCallback("/err400", "_http_400");
$http->setDefaultCallback("_http_default");

$http->bind("0.0.0.0", 9090);
$base->loop();
?>
[/php]


node.js

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        87 bytes

Concurrency Level:      500
Time taken for tests:   2.448 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      940000 bytes
HTML transferred:       435000 bytes
Requests per second:    2042.88 [#/sec] (mean)
Time per request:       244.752 [ms] (mean)
Time per request:       0.490 [ms] (mean, across all concurrent requests)
Transfer rate:          375.06 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    4  11.8      0      54
Processing:     2   46  29.7     45     912
Waiting:        2   45  29.8     45     912
Total:          2   50  33.2     47     912

Percentage of the requests served within a certain time (ms)
  50%     47
  66%     60
  75%     68
  80%     73
  90%     85
  95%    106
  98%    122
  99%    129
 100%    912 (longest request)

httpd.js
[javascript]
var http = require(‘http’);
var server = http.createServer(
function (request, response) {
response.writeHead(200, {‘Content-Type’: ‘text/plain’});
response.write(
"<html>\n" +
"<head>\n" +
"<title>libevent</title>\n" +
"</head>\n" +
"<body>\n" +
"libeventテスト\n" +
"</body>\n" +
"</html>\n"
);
response.end();
}
).listen(9090);
[/javascript]


nginx

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        nginx/1.4.1
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        168 bytes

Concurrency Level:      500
Time taken for tests:   1.084 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Non-2xx responses:      5000
Total transferred:      1585000 bytes
HTML transferred:       840000 bytes
Requests per second:    4612.45 [#/sec] (mean)
Time per request:       108.402 [ms] (mean)
Time per request:       0.217 [ms] (mean, across all concurrent requests)
Transfer rate:          1427.88 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22   38   4.9     39      53
Processing:    11   44  12.0     42     102
Waiting:        8   33   8.5     33      65
Total:         50   82  10.6     81     137

Percentage of the requests served within a certain time (ms)
  50%     81
  66%     83
  75%     84
  80%     84
  90%     91
  95%    102
  98%    123
  99%    130
 100%    137 (longest request)

shortfin

$ ab -n 5000 -c 500 http://localhost:9090/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        shortfin/0.9.5
Server Hostname:        localhost
Server Port:            9090

Document Path:          /
Document Length:        87 bytes

Concurrency Level:      500
Time taken for tests:   0.954 seconds
Complete requests:      5000
Failed requests:        0
Write errors:           0
Total transferred:      875000 bytes
HTML transferred:       435000 bytes
Requests per second:    5240.77 [#/sec] (mean)
Time per request:       95.406 [ms] (mean)
Time per request:       0.191 [ms] (mean, across all concurrent requests)
Transfer rate:          895.64 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        5    9   2.3      9      23
Processing:     2    9   2.0      9      19
Waiting:        1    7   3.1      7      19
Total:         12   18   1.9     19      40

Percentage of the requests served within a certain time (ms)
  50%     19
  66%     19
  75%     19
  80%     19
  90%     19
  95%     20
  98%     28
  99%     28
 100%     40 (longest request)

単にHTMLを出力するだけなので、差が分からない結果でした。
Shortfinが抜きに出た速度がでています。

libevent2

libeventとは
Chromium、Memcachedなどで有名なC言語で作成されたイベント駆動型フレームワークです。

以下の機能などが提供されています。
 ・ネットワーク監視
 ・I/O監視
 ・クライアント・サーバ問い合わせ
 ・HTTPサーバ
 ・SSL
 ・DNS

libeventを使ったサンプルは結構あるのですが、PHPのサンプルが少ないようでした。
PHP5とlibeventを使った、WebSocketのチャットを作成しました。

[github:WebSocket]


libevent2のインストール

$ wget http://jaist.dl.sourceforge.net/project/levent/libevent/libevent-2.0/libevent-2.0.21-stable.tar.gz
$ tar xzvf libevent-2.0.21-stable.tar.gz
$ cd libevent-2.0.21-stable
$ ./configure
$ make
$ sudo make install

php eventモジュールのインストール

$ wget http://pecl.php.net/get/event-1.7.2.tgz
$ tar xzvf event-1.7.2.tgz
$ cd event-1.7.2
$ phpize
$ ./configure
$ make
$ sudo -s
# make install
# cd /etc/php5/mods-available
# echo extension=event.so > event.ini
# cd /etc/php5/conf.d
# ln -s ../mods-available/event.ini ./20-event.ini

phpイベントループ
[php]
<?php
/**
* イベントベースの生成
*/
$base = new EventBase();

/**
* クライアント接続の生成
*/
$listener = new EventListener(
$base,
‘eventCallback’,
$base,
EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE,
-1,
‘0.0.0.0:8888’
);

/**
* ループスタート
*/
$base->dispatch();
?>
[/php]


クライアント接続イベント
[php]
<?php
/**
* クライアント接続時コールバック
*/
function eventCallback ($listener, $fd, $address, $base) {
/**
* 遠隔手続呼出処理
*/
$bev = new EventBufferEvent($base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);
$bev->setCallbacks(
‘readCallback’,
NULL,
NULL,
NULL
);
}

/**
* クライアント接続時コールバック
*/
function readCallback ($bev, $address) {
static $handshake;

if (empty($handshake)) {
/**
* ハンドシェイクの確立
*/
$buff = $bev->read(4096);
if (preg_match(‘/Sec-WebSocket-Key: ([^\s]+)\r\n/’, $buff, $matches)) {
$key = $matches[1];
}

/* 認証キー生成 */
$accept = $key.’258EAFA5-E914-47DA-95CA-C5AB0DC85B11′;
$accept = base64_encode(sha1($accept, true));

$header = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: WebSocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: {$accept}\r\n".
"\r\n";
$bev->write($header);
$handshake = true;
} else {
$frame = $bev->read(4096);
$msg = ws_decode($frame);
$bev->write(ws_encode($msg));
}
}
?>
[/php]


PHPはC言語のLibraryを簡単に試すことができて便利です。
libeventは大量の作業を効率的に処理できる素晴らしいLibraryです。

あのnode.jsもlibeventを採用しようとしたそうですが、当時はまだlibevent2が
正式版ではなく速度の出るlibevとlibeio採用しているそうです。

※libevent1からlibevent2になり速度が大幅にアップしています。


WebSocketについての解説
uuwan Lab:ハンドシェイクなどの説明
uuwan Lab:Framing Protocolなどの説明
上記のページを参考にさせて頂きました。

php版のfluentdモドキを作ってみた

アクセスログなどの収集とか色々便利なソフトとして有名なfluentdっぽいのがPHPで無いようなので作ってみました。
PHPでデーモン作るにはどうやっているのかを勉強するために、tailコマンドのFオプションを作ることから始まりました。

php5-tail

ファイル構成

/etc/
    php5-tail/
        config.php
        cache/
            access.pos
        method/
            format.php
            parse.php
            stdout.php
/usr/
    local/
       bin/
           php5-tail

Inotifyモジュールのインストール

sudo pecl install inotify

動作確認

php5-tail -c /etc/php5-tail/config.php

HTTPサーバへアクセス

curl http://localhost/

出力結果

Array
(
    [host] => 192.168.196.1
    [time] => 14/Apr/2013:10:41:54 -0700
    [path] => /
    [referer] => -
    [agent] => Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31
)

デーモン化

循環参照コレクタを有効にする

gc_enable();

起動した親プロセスを終了する

$pid = pcntl_fork();
if ($pid === -1) {
    throw new \Exception('Process could not be forked');
} else if ($pid) {
    // 親プロセス
    exit();
} else {
    // 子プロセス
}

シグナルをキャッチする

pcntl_signal(SIGHUP,  'handler');
pcntl_signal(SIGINT,  'handler');
pcntl_signal(SIGQUIT, 'handler');
pcntl_signal(SIGTERM, 'handler');
pcntl_signal(SIGTSTP, 'handler');

function handler($signo) {
    switch ($signo) {
    case SIGHUP:   // 再読み込み
        echo $signo."\n";
        break;
    case SIGINT:   // 割り込み
    case SIGQUIT:  // 終了
    case SIGTERM:  // 正常終了
    case SIGTSTP:  // サスペンド
        echo $signo."\n";
        posix_kill(posix_getpid(), SIGUSR1);
        exit();
    }
}

declare(ticks = 1);
wile (true) {
    echo '.';
}

多分この3つをいれれば、とりあえずデーモン化出来ると思います。
System_Daemon – Pearを参考にしました。

Posted in php.

PHPのプロファイリング

Gitからソースを取得

git clone https://github.com/facebook/xhprof.git

コンパイル

cd xhprof/extension/
phpize
./configure
make
sudo make install

モジュールの追加

sudo vi /etc/php5/conf.d/xhprof.ini
; configuration for php XHprof module
extension=xhprof.so
xhprof.output_dir="/var/www/xhprof.sheeps.me/log"

コールグラフ生成用ツールのインストール

sudo aptitude -y install graphviz

Webインターフェースの設定

cp -f xhprof/xhprof_html/ /var/www/xhprof

xhprof_htmlの中身を閲覧できるように設定

XHProfの実行サンプル
[php]
<?php
function bar($x) {
if ($x > 0) {
bar($x – 1);
}
}

function foo() {
for ($idx = 0; $idx < 5; $idx++) {
bar($idx);
$x = strlen("abc");
}
}

// start profiling
xhprof_enable();

// run program
foo();

// stop profiler
$xhprof_data = xhprof_disable();

// display raw xhprof data for the profiler run
print_r($xhprof_data);

$XHPROF_ROOT = realpath(dirname(__FILE__) .’/..’);
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";

// save raw data for this profiler run using default
// implementation of iXHProfRuns.
$xhprof_runs = new XHProfRuns_Default();

// save the run under a namespace "xhprof_foo"
$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");

echo "—————\n".
"Assuming you have set up the http based UI for \n".
"XHProf at some address, you can view run at \n".
"http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n".
"—————\n";
?>
[/php]

Posted in php.

PEARパッケージ管理のPyrusをインストール

sqlite3が必要になります

sudo aptitude -y install php5-sqlite

PyrusのPHARファイルをダウンロード

mkdir ~/.pear
cd ~/.pear/
wget http://pear2.php.net/pyrus.phar

Pyrusの初期設定

php pyrus.phar install

php pyrus.phar mypear ~/.pear/vendor
php pyrus.phar set bin_dir ~/.pear/vendor/bin

pyrusコマンド登録

vi ~/.pear/pyrus
#!/usr/bin/env bash

/usr/bin/env php -q ~/.pyrus/pyrus.phar $@
chmod +x ~/.pear/pyrus

Aliasでpyrusコマンド登録

alias pyrus='php -q ~/.pear/pyrus.phar'

ヘルプの表示

pyrus --help
Pyrus, the PHP manager

Usage:
  php pyrus.phar [/path/to/pyrus] [options]
  php pyrus.phar [/path/to/pyrus] [options]  [options] [args]

Options:
  -v, --verbose   increase verbosity
  -p, --paranoid  set or increase paranoia level
  -h, --help      show this help message and exit
  --version       show the program version and exit

Commands:
  install             Install a package.  Use install --plugin to install
                      plugins
  upgrade             Upgrade a package.  Use upgrade --plugin to upgrade
                      plugins
  uninstall           Uninstall a package.  Use uninstall --plugin to
                      uninstall plugins
  info                Display information about a package
  build               Build a PHP extension package from source and install
                      the compiled extension
  list-upgrades       List packages with upgrades available
  remote-list         List all remote packages in a channel, organized by
                      category
  download            Download a remote package to the current directory
  list-packages       List all installed packages in all channels
  list-channels       List all discovered channels
  channel-discover    Discover a new channel
  channel-del         Remove a channel from the registry
  upgrade-registry    Upgrade an old PEAR installation to the new registry
                      format
  run-scripts         Run all post-install scripts for a package
  set                 Set a configuration value
  get                 Get configuration value(s). Leave blank for all
                      values
  mypear              Set a configuration value
  help                Get help on a particular command, or all commands
  search              Search a registry of PEAR channels for packages
  make                Create or update a package.xml from a standard PEAR2
                      directory layout
  pickle              Create or update a package.xml and then package a
                      PECL extension release
  package             Create a release from an existing package.xml
  run-phpt            Run PHPT tests
  generate-pear2      Generate the source layout for a new
                      Pyrus-installable package
  generate-ext        Generate the source layout for a new PHP extension
                      that is PECL-ready
  scs-update          Simple channel server: Update all releases of a
                      within the get/ directory.
  scs-create          Simple channel server: Create a channel.xml, get/ and
                      rest/ directory for a channel
  scs-add-maintainer  Simple Channel Server: Add a new maintaing developer
                      to the channel
  scs-add-category    Simple Channel Server: Add a new category to the
                      channel
  scs-categorize      Simple Channel Server: Categorize a package
  scs-release         Simple Channel Server: Release a package

Unknown command: --help

Pyrus, the PHP manager

Usage:
  php pyrus.phar [/path/to/pyrus] [options]
  php pyrus.phar [/path/to/pyrus] [options]  [options] [args]

Options:
  -v, --verbose   increase verbosity
  -p, --paranoid  set or increase paranoia level
  -h, --help      show this help message and exit
  --version       show the program version and exit

Commands:
  install             Install a package.  Use install --plugin to install
                      plugins
  upgrade             Upgrade a package.  Use upgrade --plugin to upgrade
                      plugins
  uninstall           Uninstall a package.  Use uninstall --plugin to
                      uninstall plugins
  info                Display information about a package
  build               Build a PHP extension package from source and install
                      the compiled extension
  list-upgrades       List packages with upgrades available
  remote-list         List all remote packages in a channel, organized by
                      category
  download            Download a remote package to the current directory
  list-packages       List all installed packages in all channels
  list-channels       List all discovered channels
  channel-discover    Discover a new channel
  channel-del         Remove a channel from the registry
  upgrade-registry    Upgrade an old PEAR installation to the new registry
                      format
  run-scripts         Run all post-install scripts for a package
  set                 Set a configuration value
  get                 Get configuration value(s). Leave blank for all
                      values
  mypear              Set a configuration value
  help                Get help on a particular command, or all commands
  search              Search a registry of PEAR channels for packages
  make                Create or update a package.xml from a standard PEAR2
                      directory layout
  pickle              Create or update a package.xml and then package a
                      PECL extension release
  package             Create a release from an existing package.xml
  run-phpt            Run PHPT tests
  generate-pear2      Generate the source layout for a new
                      Pyrus-installable package
  generate-ext        Generate the source layout for a new PHP extension
                      that is PECL-ready
  scs-update          Simple channel server: Update all releases of a
                      within the get/ directory.
  scs-create          Simple channel server: Create a channel.xml, get/ and
                      rest/ directory for a channel
  scs-add-maintainer  Simple Channel Server: Add a new maintaing developer
                      to the channel
  scs-add-category    Simple Channel Server: Add a new category to the
                      channel
  scs-categorize      Simple Channel Server: Categorize a package
  scs-release         Simple Channel Server: Release a package
Posted in php.

phpを利用したMapReduce実行

サンプルデータの配置

サンプルデータの作成

echo Hello World Bye World > file01
echo Hello Hadoop Goodbye Hadoop > file02

ls
file01  file02

HDFS上にinputディレクトリを作成

sudo -u hdfs hadoop fs -mkdir /user/hdfs/input

HDFS上にサンプルデータを配置

sudo -u hdfs hadoop fs -put file01 /user/hdfs/input/file01
sudo -u hdfs hadoop fs -put file02 /user/hdfs/input/file02

sudo -u hdfs hadoop fs -cat /user/hdfs/input/file01
Hello World Bye World

sudo -u hdfs hadoop fs -cat /user/hdfs/input/file02
echo Hello Hadoop Goodbye Hadoop

map処理の作成

vi map.php

[php]
<?php
while (($row = fgetcsv(STDIN, 1024, " ")) !== FALSE) {
foreach ($row as $word) {
if ($word !== ”) {
echo "${word}\t1\n";
}
}
}
?>
[/php]

map.phpローカルテスト

cat file01 file02 | php ./map.php

Hello   1
World   1
Bye     1
World   1
Hello   1
Hadoop  1
Goodbye 1
Hadoop  1

キーと値のペアが出力される。
値は文字の出現回数とし1を固定。

map処理と同じ状態で出力

cat file01 file02 | php ./map.php | sort

Bye     1
Goodbye 1
Hadoop  1
Hadoop  1
Hello   1
Hello   1
World   1
World   1

キーを元にソートされるためsortコマンドへ送る。

reduce処理の作成

vi reduce.php

[php]
<?php
$count = array();
while ((list($key, $value) = fgetcsv(STDIN, 1024, "\t")) !== FALSE) {
$count[$key] = empty($count[$key]) ? 1: $count[$key] + 1;
}

foreach ( $count as $key => $value ) {
echo "${key}\t${value}\n";
}
?>
[/php]

reduce.phpローカルテスト

cat file01 file02 | php ./map.php | sort | php ./reduce.php

Bye     1
Goodbye 1
Hadoop  2
Hello   2
World   2

キーと値のペアを配列にマップしてカウント

Hadoop Streamingの実行

ファイルの配信

scp -r /home/mapred hdfs@slaves000:/home/
scp -r /home/mapred hdfs@slaves001:/home/
scp -r /home/mapred hdfs@slaves002:/home/

ストリーミングモジュールを利用してmapreduceの実行

sudo su hdfs

/usr/lib/hadoop-0.20/bin/hadoop \
  jar /usr/lib/hadoop-0.20/contrib/streaming/hadoop-streaming-0.20.2-cdh3u5.jar \
  -input /user/hdfs/input \
  -output /user/hdfs/output \
  -mapper '/usr/bin/php /home/mapred/map.php' \
  -reducer '/usr/bin/php /home/mapred/reduce.php'

/user/hdfs/outputが既に存在しているとエラーになります。

結果の確認

sudo -s hdfs hadoop fs -ls /user/hdfs/output
Found 3 items
-rw-r--r--   1 hdfs supergroup          0 2012-12-02 04:23 /user/hdfs/output/_SUCCESS
drwxr-xr-x   - hdfs supergroup          0 2012-12-02 04:24 /user/hdfs/output/_logs
-rw-r--r--   1 hdfs supergroup         41 2012-12-02 04:25 /user/hdfs/output/part-00000

sudo -u hdfs hadoop fs -cat /user/hdfs/output/part-00000

Bye     1
Goodbye 1
Hadoop  2
Hello   2
World   2

HDFS上の/user/hdfs/output/に結果が保存される。