BashのProcess SubstitutionとWebAPI向けテストへの一考察

WebAPI(JSONが返ってくる感じのもの)のテストを簡単に書きたいなと思いまして、様々な言語やテストフレームワークを見ていたんですが、Bashで書けば書く方も読む方も楽かなと思いましていろいろとやってみましたらできそうな感じだったので記録に残しておきます。まあ知っている人にとっては極めて常識的な内容ですが、working exampleとして。。。

diff.sh

#!/usr/bin/env bash

diff <(echo "fuga") <(echo "{\"piyo\":\"fuga\", \"foo\":\"bar\"}" | jq -r '.piyo')

$ chmod +x diff.sh 
$ ./diff.sh
# 何も出ませんが、差分がないということなので、正常です。

↑こんな感じでBashのProcess Substitutionを使って、jqで一部分を取り出すことが出来ました。jqのオプションの-rは、raw outputという意味で、ダブルクオートなしで出力してくれます。WebAPIのテストでは、↑のechoでJSONを吐いている部分を、curlなどでWebAPIを叩く処理を書けば行けそうです。

注意点としては、

– shとして実行するとエラーが出ます。bashとして実行する必要があります。

また、初期値を設定してそれを参照したいところですが、↓のような感じで出来ます。

# ↓のようなファイルを作り、
$ vi users.txt 
$ cat users.txt 
EMAIL01=foo@example.com
PASSWORD01=secret_password
EMAIL02=bar@example.com
PASSWORD02=secret_p@ssword

# ↓さらにusers.txtを参照するスクリプトを作り
$ vi hoge.sh 
$ cat hoge.sh 
. ./users.txt
echo $EMAIL01
echo $PASSWORD01
echo $EMAIL02
echo $PASSWORD02

# 実行すると、users.txtに記述しておいた内容をシェルスクリプトで参照できます。
$ sh hoge.sh 
foo@example.com
secret_password
bar@example.com
secret_p@ssword

↑これで、username+passwordでログインするAPIを叩いたりできますね。

Debianにmediawiki 1.24.1をインストール

WikipediaのCMSであるMediaWikiを使ってみようと思いました。入れてみた感想としては微妙な感じですが。。。

– デザインが古臭い
– ファイルのアップロードが編集中の画面で完結せず、ファイル名のコピーアンドペーストなどが必要で煩雑
– もっさりしていて遅い

などなどです。どうやったらこんなに遅くなるのかってぐらい遅いですね。wrkでベンチマークしてみますとOpteron 3280マシンで5~10req/secという遅さです。1reqに1sec近くも掛かっています。。。単なるHello, PHPと返すPHPスクリプトは平均16.78msで応答するのですが(一番下にベンチマーク結果を置いておきます)。

想定環境は

– Debian系
– Apache

です。まあともかく入れてみます。MediaWikiはPHPとMySQLに依存するので、それらをまず入れます。以下はrootでやってください。

必要なライブラリやソフトウェアのインストール↓

$ sudo aptitude install mysql-server php5 imagemagick php5-imagick php5-mysql php-apc php5-intl
$ cd /var/www/
$ wget http://releases.wikimedia.org/mediawiki/1.24/mediawiki-1.24.1.tar.gz
$ tar zxvf mediawiki-1.24.1.tar.gz
$ mv mediawiki-1.24.1 wiki

MySQLの設定(username=wikiuser, password=wikipassword, database name = wikidbとします)↓
$ service mysql start
$ mysql -uroot -p
mysql> GRANT ALL PRIVILEGES ON *.* TO wikiuser@'localhost' IDENTIFIED BY 'wikipassword' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE DATABASE wikidb CHARACTER SET utf8;
Query OK, 1 row affected (0.00 sec)

ブラウザで、http://<マシンのIPアドレス>/wiki/にアクセスして、「Please set up the wiki first.」をクリック

Your language (インストール時に使う言語)とWiki languageを選択、ひとまず英語で良いと思う

右下のcontinueをクリック

MySQLのセッテイングで、Database host→127.0.0.1、Database name→wikidb、Database table prefixは何も入力しない、Database username→wikiuser、Database password→wikipassword として右下のcontinueをクリック

Storage engineはInnoDBを選択、Database character setはBinaryを選択 (utf-8でもいいかも)して右下のcontinueをクリック

Name of wikiを適当に入力、たとえば Group Wiki など。Project namespaceはよく分からないので Same as the wiki nameを選択、Your Username、Passwordを適当に入力、email addressは自由、そして右下のcontinueをクリック

User rights profileはAccount creation requiredが良いかと。その他いろいろと見て設定してcontinue

Installと出るのでcontinue(ちょっと時間が掛かります)

LocalSettings.phpというファイルがダウンロードされる。その内容をコピーして、/var/www/wikiに、vi LocalSettings.phpとして貼り付ける。

再度ブラウザでアクセス

右上のLog inをクリックしてログイン

以上です。

蛇足

ベンチマークしてみます。今回簡単のため、一台のマシン(Opteron 3280)でサーバーも計測も行っています。MediaWikiの入っているマシンのIPアドレスを192.168.1.2とします。

– Hello, PHP!という文字列を返すだけのhello.php (http://192.168.1.2/hello.php)
– MediaWikiで作ったMemberというページ (http://192.168.1.145/wiki/index.php/Member)

の比較をしてみます。まずはhello.phpの結果↓

$ curl http://192.168.1.2/hello.php
Hello, PHP!

$  ./wrk -c 100 -d 5 -t 3 http://192.168.1.2/hello.php
Running 5s test @ http://192.168.1.2/hello.php
  3 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    16.78ms   86.70ms 532.88ms   96.42%
    Req/Sec    10.28k     3.43k   21.11k    67.19%
  143600 requests in 5.00s, 28.10MB read
Requests/sec:  28717.53
Transfer/sec:      5.62MB

次に、Memberの結果です↓。

$ curl http://192.168.1.145/wiki/index.php/Member
中略
$ ./wrk -c 5 -d 5 -t 1 http://192.168.1.145/wiki/index.php/Member
Running 5s test @ http://192.168.1.145/wiki/index.php/Member
  1 threads and 5 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   902.04ms  135.29ms 997.71ms  100.00%
    Req/Sec     5.00      0.00     5.00    100.00%
  27 requests in 5.22s, 386.31KB read
Requests/sec:      5.17
Transfer/sec:     73.96KB

↑いやあ、酷いですね。APCが有効になっているはずなのですが。。。

【Redis】Raspberry Pi 2 Model Bのベンチマーク【UnixBench】

大幅に性能が向上したラズパイ2,さっそくベンチマークしてみます。ひとまず、

– メモリ帯域
– ディスク帯域
– Redis
– UnixBench

です。

– ラズパイ2のセットアップは前回の記事にあります→ Raspberry Pi 2 Model Bのセットアップ
– ラズパイ2とH2Oを使って毎秒2万リクエストを達成した記事は→ Raspberry Pi 2 Model B と 高速HTTPサーバーH2Oで毎秒2万リクエスト

まずは帯域系を計測してみます。

$ uname -a
Linux raspberrypi 3.18.7-v7+ #755 SMP PREEMPT Thu Feb 12 17:20:48 GMT 2015 armv7l GNU/Linux

$ dd if=/dev/zero of=/dev/null bs=1024K count=100000
100000+0 records in
100000+0 records out
104857600000 bytes (105 GB) copied, 100.467 s, 1.0 GB/s

$  dd if=/dev/zero of=deleteme bs=32M count=100
100+0 records in
100+0 records out
3355443200 bytes (3.4 GB) copied, 288.803 s, 11.6 MB/s

↑帯域等は旧版とさほど変わりがない印象です。microSDカードは、東芝のSD-C016GR7AR040Aです。16GBで、パッケージにはclass10、Read 40MB/sと書いてあります。たしか900円ぐらいでした。ただし、ラズパイ2はUHS-Iに対応していない感じで、40MB/sも出ませんね。というわけで、class10のmicroSDカードで十分だと思います。お店によっては32GBを1000円以下で購入できるのではないでしょうか。

次はRedisです。

$ wget http://download.redis.io/releases/redis-2.8.19.tar.gz
$ tar zxvf redis-2.8.19.tar.gz
$ cd redis
$ make
$ ./src/redis-server
$ ./src/redis-benchmark -q
PING_INLINE: 13993.84 requests per second
PING_BULK: 13943.11 requests per second
SET: 13133.70 requests per second
GET: 14062.72 requests per second
INCR: 12559.66 requests per second
LPUSH: 12680.70 requests per second
LPOP: 13054.83 requests per second
SADD: 13419.22 requests per second
SPOP: 13823.61 requests per second
LPUSH (needed to benchmark LRANGE): 12768.13 requests per second
LRANGE_100 (first 100 elements): 4423.60 requests per second
LRANGE_300 (first 300 elements): 1672.35 requests per second
LRANGE_500 (first 450 elements): 1155.72 requests per second
LRANGE_600 (first 600 elements): 871.10 requests per second
MSET (10 keys): 7089.68 requests per second

↑旧版ラズパイでは、GETは約2000req/secでした。だいぶ向上していますね。ただし注意点として、旧版ラズパイは1コアであり、1コアでベンチマークのserver/clientの両方を動かしていたわけでその影響でより遅く見えるのもあるかと思います。ラズパイ2のRedisベンチマークでは、Redis自体はマルチコア非対応で1コアで動くこと、さらにラズパイ2は4コアあるので、server/clientにそれぞれ1コアずつ割り当てられて、正確な計測が出来たという可能性があります。

次はUnixBenchです。

$ wget http://byte-unixbench.googlecode.com/files/UnixBench5.1.3.tgz
$ tar zxvf UnixBench5.1.3.tgz 
$ cd UnixBench
$ ./Run
========================================================================
   BYTE UNIX Benchmarks (Version 5.1.3)

   System: raspberrypi: GNU/Linux
   OS: GNU/Linux -- 3.18.7-v7+ -- #755 SMP PREEMPT Thu Feb 12 17:20:48 GMT 2015
   Machine: armv7l (unknown)
   Language: en_US.utf8 (charmap="UTF-8", collate="UTF-8")
   CPU 0: ARMv7 Processor rev 5 (v7l) (0.0 bogomips)
          
   CPU 1: ARMv7 Processor rev 5 (v7l) (0.0 bogomips)
          
   CPU 2: ARMv7 Processor rev 5 (v7l) (0.0 bogomips)
          
   CPU 3: ARMv7 Processor rev 5 (v7l) (0.0 bogomips)
          
   10:27:40 up  1:03,  2 users,  load average: 0.19, 1.35, 1.49; runlevel 2

------------------------------------------------------------------------
Benchmark Run: Sat Feb 14 2015 10:27:40 - 10:55:53
4 CPUs in system; running 1 parallel copy of tests

Dhrystone 2 using register variables        2956973.9 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                      498.2 MWIPS (9.9 s, 7 samples)
Execl Throughput                                360.6 lps   (29.8 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks         72541.4 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks           20956.4 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks        189365.6 KBps  (30.0 s, 2 samples)
Pipe Throughput                              175922.0 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                  31694.4 lps   (10.0 s, 7 samples)
Process Creation                               1234.8 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   1107.6 lpm   (60.0 s, 2 samples)
Shell Scripts (8 concurrent)                    316.1 lpm   (60.1 s, 2 samples)
System Call Overhead                         410678.3 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0    2956973.9    253.4
Double-Precision Whetstone                       55.0        498.2     90.6
Execl Throughput                                 43.0        360.6     83.9
File Copy 1024 bufsize 2000 maxblocks          3960.0      72541.4    183.2
File Copy 256 bufsize 500 maxblocks            1655.0      20956.4    126.6
File Copy 4096 bufsize 8000 maxblocks          5800.0     189365.6    326.5
Pipe Throughput                               12440.0     175922.0    141.4
Pipe-based Context Switching                   4000.0      31694.4     79.2
Process Creation                                126.0       1234.8     98.0
Shell Scripts (1 concurrent)                     42.4       1107.6    261.2
Shell Scripts (8 concurrent)                      6.0        316.1    526.8
System Call Overhead                          15000.0     410678.3    273.8
                                                                   ========
System Benchmarks Index Score                                         170.5

------------------------------------------------------------------------
Benchmark Run: Sat Feb 14 2015 10:55:53 - 11:24:12
4 CPUs in system; running 4 parallel copies of tests

Dhrystone 2 using register variables       11802735.6 lps   (10.0 s, 7 samples)
Double-Precision Whetstone                     1988.5 MWIPS (9.9 s, 7 samples)
Execl Throughput                               1359.5 lps   (29.9 s, 2 samples)
File Copy 1024 bufsize 2000 maxblocks        115667.5 KBps  (30.0 s, 2 samples)
File Copy 256 bufsize 500 maxblocks           32669.0 KBps  (30.0 s, 2 samples)
File Copy 4096 bufsize 8000 maxblocks        313050.3 KBps  (30.0 s, 2 samples)
Pipe Throughput                              698307.4 lps   (10.0 s, 7 samples)
Pipe-based Context Switching                 122975.8 lps   (10.0 s, 7 samples)
Process Creation                               2890.8 lps   (30.0 s, 2 samples)
Shell Scripts (1 concurrent)                   2536.4 lpm   (60.1 s, 2 samples)
Shell Scripts (8 concurrent)                    333.3 lpm   (60.3 s, 2 samples)
System Call Overhead                        1579952.2 lps   (10.0 s, 7 samples)

System Benchmarks Index Values               BASELINE       RESULT    INDEX
Dhrystone 2 using register variables         116700.0   11802735.6   1011.4
Double-Precision Whetstone                       55.0       1988.5    361.5
Execl Throughput                                 43.0       1359.5    316.2
File Copy 1024 bufsize 2000 maxblocks          3960.0     115667.5    292.1
File Copy 256 bufsize 500 maxblocks            1655.0      32669.0    197.4
File Copy 4096 bufsize 8000 maxblocks          5800.0     313050.3    539.7
Pipe Throughput                               12440.0     698307.4    561.3
Pipe-based Context Switching                   4000.0     122975.8    307.4
Process Creation                                126.0       2890.8    229.4
Shell Scripts (1 concurrent)                     42.4       2536.4    598.2
Shell Scripts (8 concurrent)                      6.0        333.3    555.6
System Call Overhead                          15000.0    1579952.2   1053.3
                                                                   ========
System Benchmarks Index Score                                         438.0

旧版ラズパイのSystem Benchmarks Index Scoreは72.9〜104.7だったので、4〜6倍程度ですね。もちろん、UnixBenchがコンピュータの性能指標の全てではありませんが。。。

Raspberry Pi 2 Model Bのセットアップ

覚書です。ひとまず使える状態に持っていく方法です。

http://www.raspberrypi.org/downloads/ のNOOBSをダウンロード

NOOBSを解凍してその中身を、microSDカードのルートディレクトリに入れる

ラズパイにmicroSDカードとHDMIケーブルとキーボードとマウスを挿して、microUSBケーブルを電源につないで起動

GUIが出るので、Raspbianをインストール

しばらく待つと終了

Raspbianが起動する。コンソール上にGUIがでるが、特に設定せずにtabを押してfinishを選択してenter。

コンソールで、$ sudo su として、次に# passwdとしてルートパスワードを変更

コンソールで、$ aptitude update; aptitude upgrade -y

コンソールで、$ aptitude install vim

コンソールで、ネットワークを設定する (静的に割り当てる場合。DHCPならば何もしなくてよい)

– 割り当てるIPアドレス: 192.168.1.146
– ネットワーク: 192.168.1.0
– ネットマスク: 255.255.255.0
– ブロードキャスト: 192.168.1.255
– ゲートウェイ(ルーター): 192.168.1.1
– DNSサーバー: 192.168.0.1
としたい場合は、

$ vi /etc/network/interfaces 
$ cat /etc/network/interfaces 
auto lo

iface lo inet loopback

auto eth0
iface eth0 inet static
address 192.168.1.146
network 192.168.1.0
netmask 255.255.255.0
broadcast 192.168.1.255
gateway 192.168.1.1
dns-nameservers 192.168.0.1

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp

$ service networking restart

とする。

おわり

Raspberry Pi 2 Model B と 高速HTTPサーバーH2Oで毎秒2万リクエスト

Raspberry Pi 2 Model B が届いてベンチマークを取ってみました。前回H2OというHTTPサーバーで遊んでみましたが、Raspberry Pi 2でそれをやってみたところ、2万req/secを達成出来ました。OSはRasbian(Linux raspberrypi 3.18.7-v7+ #755 SMP PREEMPT Thu Feb 12 17:20:48 GMT 2015 armv7l GNU/Linux
)を使用しました。CPUのオーバークロックなどはしていません。

まずはインストール・立ち上げ方法を書いておきます。

$ sudo aptitude install cmake make gcc c++ libyaml-dev git
$ git clone git clone https://github.com/h2o/h2o
$ cd h2o
# ↓cmakeでピリオドを忘れないようにしてください。
$ cmake .
$ make

# (蛇足ですが↑でmake -jなどとして高速なコンパイルをしようとするとエラーが出ました)

# confの設定。一旦オリジナルを保持してから新しく作る。
$ cp examples/h2o/h2o.conf examples/h2o/h2o.conf.orig
$ vi examples/h2o/h2o.conf
$ cat examples/h2o/h2o.conf
user: root
http2-max-concurrent-requests-per-connection: 1024
num-threads: 4
listen: 8080
hosts:
  "0.0.0.0.xip.io:8080":
    paths:
      /:
        file.dir: examples/doc_root

# 'Hello, world.'という中身のファイルを作成
$ echo 'Hello, world.' > examples/doc_root/index.html

# 起動スクリプトの設定
$ echo './h2o -c examples/h2o/h2o.conf' > run.sh
$ chmod +x run.sh

# 起動
$ ./run.sh
[INFO] raised RLIMIT_NOFILE to 4096
h2o server (pid:2422) is ready to serve requests

↑さて、別のマシンでベンチマークしてみます。

トポロジーは、

Raspberry Pi 2 <=> GbEスイッチングハブ <=> クライアント(Opteron 3280, 8cores@2.4GHz、Ubuntu 12.04.5)

です。ラズパイとスイッチングハブ間は100Mbpsになっています。ラズパイのNICが100Mだからです。スイッチングハブとクライアントの間は1Gbpsです。つまりラズパイ<=>クライアントの帯域は100Mbpsとなります。

ベンチマークツールには、wrkというものを使います。

以下、ベンチマークです。クライアントマシンから一旦正常に通信できるか確かめて、次にベンチマークを取ります。

# 以下、クライアントから実行しています。
# ラズパイのIPアドレスは192.168.1.145だったとします。
$ curl http://192.168.1.145:8080/
Hello, world.

# wrkベンチマーク
$ ./wrk -c 100 -d 10 -t 5 http://192.168.1.145:8080/
Running 10s test @ http://192.168.1.145:8080/
  5 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.85ms  613.12us  15.60ms   73.90%
    Req/Sec     4.32k   272.86     5.50k    63.81%
  204553 requests in 10.00s, 45.06MB read
Requests/sec:  20455.96
Transfer/sec:      4.51MB

Requests/sec: 20455.96 !!!
というわけでラズパイ2とH2Oで毎秒2万リクエストを達成しました\(^o^)/

これまでの挑戦では毎秒1.9万ぐらいで足踏みしていたのですが、ラズパイ2を再起動してみてベンチマークを走らせたらあっさり到達出来ました。時代はリブートテクノロジーですね。

カーネルオプションはいろいろといじってみたりしましたが、効果がありませんでした。試みてみたもの一覧が以下になります。

sysctl -w fs.file-max=9999999
sysctl -w fs.nr_open=9999999
sysctl -w net.core.netdev_max_backlog=4096
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.somaxconn=65535
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.ip_forward=0
sysctl -w net.ipv4.ip_local_port_range=1025 65535
sysctl -w net.ipv4.tcp_fin_timeout=30
sysctl -w net.ipv4.tcp_keepalive_time=30
sysctl -w net.ipv4.tcp_max_syn_backlog=20480
sysctl -w net.ipv4.tcp_max_tw_buckets=400000
sysctl -w net.ipv4.tcp_no_metrics_save=1
sysctl -w net.ipv4.tcp_syn_retries=2
sysctl -w net.ipv4.tcp_synack_retries=2
sysctl -w net.ipv4.tcp_tw_recycle=1
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w vm.min_free_kbytes=65536
sysctl -w vm.overcommit_memory=1

ちなみにapache2 (Server version: Apache/2.2.22 (Debian), Server built: Jan 10 2015 15:51:04)では、

$ curl http://192.168.1.145/index.html
Hello, world.

$ ./wrk -c 100 -d 10 -t 3 http://192.168.1.145/index.html
Running 10s test @ http://192.168.1.145/index.html
  3 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    19.38ms  133.78ms   5.42s    99.31%
    Req/Sec     1.71k   268.19     2.54k    66.11%
  50716 requests in 10.00s, 13.01MB read
  Socket errors: connect 0, read 0, write 0, timeout 32
Requests/sec:   5071.31
Transfer/sec:      1.30MB

程度でした。-t 3と下げてますが(-tはthread数)、それでもtimeoutが出ていますね。

また、mod_phpですと、

$ curl http://192.168.1.145/index.php
Hello, PHP.

$ ./wrk -c 100 -d 10 -t 1 http://192.168.1.145/index.php
Running 10s test @ http://192.168.1.145/index.php
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    49.77ms   50.72ms 232.25ms   82.86%
    Req/Sec     1.96k    24.84     2.02k    75.00%
  19403 requests in 10.00s, 3.74MB read
Requests/sec:   1939.90
Transfer/sec:    382.68KB

でした。-t 1にしているのは、それ以上だとtimeoutがたくさん出るからです。

OpenResty(ngx_openresty-1.7.7.2)ですと、

$ curl http://192.168.1.145:8080/
Hello, world!

$ ./wrk -c 100 -d 10 -t 4 http://192.168.1.145:8080/
Running 10s test @ http://192.168.1.145:8080/
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.85ms   22.93ms 793.53ms   97.85%
    Req/Sec     4.49k   746.82     7.28k    69.50%
  173783 requests in 10.00s, 30.49MB read
  Socket errors: connect 0, read 0, write 0, timeout 4
Requests/sec:  17378.24
Transfer/sec:      3.05MB

でした。OpenRestyのnginx.confは、

cat conf/nginx.conf 
error_log logs/error.log;
worker_processes 4;
 
events {
    worker_connections 1024;
    accept_mutex_delay 100ms;
}
http {
    keepalive_requests 500000;
    tcp_nopush on;
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua 'ngx.say("Hello, world!")';
        }
    }
}

でした。