Webアプリケーションのパフォーマンス測定ツールFunkload...

# Funkload

Webアプリケーションのパフォーマンス測定ツールについて(サーバー側の負荷も兼ねてだけど)、[Apache Bench](http://httpd.apache.org/docs/2.4/programs/ab.html)や[Siege](http://www.joedog.org/siege-home/)や[JMeter](http://jmeter.apache.org/)があるけど、
それ以外に何かねえのかな、と思ってStackOverflowを調べていたら
[Funkload](https://github.com/nuxeo/FunkLoad)というものを見つけた。

Python製らしい。なら試しに調査してみるか、ということでいじってみた。

# 動作環境整備

動作環境として、自分自身の環境は以下の通り。

- Mac OS X 10.8
- Python 2.7.5

FunkloadはPython 2.x系をサポートしているようなので、2.x系最新の(2013年6月現在の)バージョンを選択する。
[Funkloadのドキュメントに載っているMac OS Xでのインストール方法](http://funkload.nuxeo.org/installation.html#installing-on-mac)を参考にする。
...どうやらgnuplotさえ入れられれば良いようだ。homebrewで入れてしまおう。

```sh
> brew install gnuplot
```

## 汚れても良い環境を作る

[virtualenv](https://pypi.python.org/pypi/virtualenv)にて、汚れても良い環境を作る。

システムにある(homebrewなどでインストールしている) Python 2.7.5にて
easy_installを利用しているPythonの処理系でインストールしていない場合は、
[distributeパッケージ](https://pypi.python.org/pypi/distribute)をダウンロードしてインストールし、以下の形で
easy_install, pipをインストールする

```
> curl -O http://python-distribute.org/distribute_setup.py
> python distribute_setup.py
> rehash
> easy_install pip
> rehash
> pip install virtualenv
```

## Funkloadのインストール

virtualenvが入っている場合は、以下の形で
pipコマンドを利用してFunkloadをインストールする。

```
> mkdir ~/Sandbox/Funkload
> cd ~/Sandbox/Funkload
> virtualenv .venv
> source .venv/bin/activate
> pip install funkload
```

# Funkloadをひとまずテスト

## テスト用のWebアプリケーションをひとまず準備

テスト用のWebアプリがないとしょうがないので、
express(node.js)のskelton作成機能を利用してテスト用アプリをこさえる。
nodeの環境が構築されていない場合、[nodebrew](https://github.com/hokaccha/nodebrew)を使うのがオススメです…

### expressを入れていない場合

Globalな場所にnpm_modulesとしてインストールしましょう。
ようはまあnpm install。

```
> npm install -g express
```

## テストアプリ作成

skelton作成機能でお手軽(Scaffoldとは公式には書いてなかった)

```
> express webserver
> cd webserver && npm install
> node app
```

以上でテストアプリ起動は終わり。

![express application start](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-1.png)

## テストケースの作成

FunkLoadのテストケースを[FunkloadのTutorial](http://funkload.nuxeo.org/tutorial.html)を見ながら
書くことにする。
コマンドでテストケースのひな形を作れる。良いですね。

```
> fl-install-demo
> cd funkload-demo/simple
```

作成されたテストケースについて、Simple.confファイルのmainセクションについて以下の形に書き換える。

```
[main]
title=Express skelton page test
description=express.js skelton application testing a root page.
# the server url to test
url=http://localhost:3000/
```

## Funkloadを起動してアプリへのアクセスをテスト

以下のコマンドを実行しよう

```
> fl-run-test -dv test_Simple.py
```

![fl-run-test](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-2.png)

Webアプリケーションへのテストケースは問題無さそうだ。

## Funkloadで実際にテスト

実際にテストを行なってみよう。

```
> fl-run-bench -c 1:10:20 test_Simple.py Simple.test_simple
```

-c オプションは、立ち上げるテスト向けのスレッド数オプションで、
以上の例だと1スレッド、10スレッド、20スレッドでのテストを行う形になる。

![fl-run-bench](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-3.png)

実際のテストについても無事終わった。　

## テストリポートの生成

テストレポートを以下のコマンドでxmlファイルをhtmlファイルとして変換する。

```
> fl-build-report --html simple-bench.xml
```

ディレクトリが作成され、その下に
htmlファイルの形式でレポートが生成される。
実際に見てみよう...

```
> cd test_simple-20130601T104942
> open index.html
```

![report-1](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-4.png)

おお、何か出力されている…
page statsは取得に成功したページの数なんだろうけど、
EとかGとかわからねえ、と思ったら出力したhtmlファイルの下部にきちんと説明の記載があった。

![report-2](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-5.png)

Score 0.94以上であればEということで青い棒が立っているようだ。
シンプルであり且つ想定ユーザー数も20とか少ないし、大抵レスポンスの速度に問題ないから
そうなるか。

# 少し複雑なアプリケーションを書いて、それに対してテストしてみたい

ということで、少し複雑なアプリケーションを書いてそれに対してテストを行う
シナリオを用意してみようと思う。

複雑なアプリケーションについては以下に用意した。
[https://github.com/futoase/bottle-application-sample](https://github.com/futoase/bottle-application-sample)
単純にフォームがあり、書き込みが行われたら表示する、というもの。

![Comment Board](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-6.png)

既にテストシナリオ/アプリケーションについて用意したものをgithubに登録した。
以下のリポジトリをgit clone && git submodule update --initをして確認できるようにしてる。

[https://github.com/futoase/funkload-sample](https://github.com/futoase/funkload-sample)

## シナリオを書く

条件として、対象となるWebアプリケーションは以下の機能がある。

- user_name, commentを/commentでPOST methodで受け取り、sqlite3 DBファイルに追記する
- / (index) にて投稿されたcommentの内容を表示

単純だ。以上の条件をチェックするためのシナリオを書こう

- / にアクセスする(GETにてリソースを取得する)
- /commentに対しuser_name, commentの内容をPOSTし、レコードに追記する

注意しないと行けない点として、

- テストで利用するファイル名、クラス名、設定ファイル名は同一にしないと行けない。
- 設定ファイル(conf)に書くmainセクションは省くことはできない　

mainセクションに書かれている内容が省くことができないの、
[ソースコードにハードコーディングされているため](https://github.com/nuxeo/FunkLoad/blob/1.16.1/src/funkload/BenchRunner.py#L229)だった…

### シナリオファイル

CommentBoard.pyにシナリオを書いている。
テストシナリオはの[Funkloadのドキュメントページ](http://funkload.nuxeo.org/writing-test.html)を参考にしている。

```python:CommentBoard.py
# -*- coding:utf-8 -*-

import unittest
import random
import json
from funkload.FunkLoadTestCase import FunkLoadTestCase

with open('conf/user_names.json') as f:
  USER_NAMES = json.loads(f.read())

with open('conf/comments.json') as f:
  COMMENTS = json.loads(f.read())

class CommentBoard(FunkLoadTestCase):
  def setUp(self):
    self.target_url = self.conf_get('main', 'url')
    self.try_count = self.conf_getInt('comment_board_setting', 'try_count')

  def test_index(self):
    target_url = self.target_url
    try_count = self.try_count

    for i in range(try_count):
      self.get(target_url, description='Get comment board index.') 

  def test_posts(self):
    target_url = self.target_url + 'comment'
    try_count = self.try_count

    user_name = random.choice(USER_NAMES)
    comment = random.choice(COMMENTS)

    self.post(target_url,
      params=[
        ('user_name', user_name),
        ('comment', comment)
      ],
      description='Comment as {user_name}'.format(user_name=user_name)
    )

if __name__ == '__main__':
  unittest.main()
```

- test_indexが/(index)に対するテストシナリオ
- test_postsが/commentに対するテストシナリオ

となる。

### シナリオで使うための設定ファイル

CommentBoard.confに設定ファイルを書いている。

```
[main]
title=Comment board test
description=Comment board test
url=http://localhost:3000/

[index]
title=Comment board index tests
description=Comment board index

[comments]
title=Comment board posts tests
description=Comment board posts

[comment_board_setting]
try_count=30

[credential]
log_to = console file
log_path = comment-board.log
result_path = comment-board-test.xml
ok_codes =  200:301:302
sleep_time_min = 0
sleep_time_max = 0

[bench]
cycles = 50:75:100:125
duration = 10
startup_delay = 0.01
sleep_time = 0.01
cycle_time = 1
log_to =
log_path = comment-board-bench.log
result_path = comment-board-bench.xml
ok_codes =  200:301:302
sleep_time_min = 0
sleep_time_max = 0.5

[distribute]
log_path = log-distributed
funkload_location=http://pypi.python.org/packages/source/f/funkload/funkload-1.16.1.tar.gz
```

あんまこれといって特殊なことはしていない。

## テスト実行

テストの実行前に、テスト対象のアプリケーションを立ちあげなければいけないので、
以下の要領で立ち上げよう。

```
> git clone git://github.com/futoase/funkload-sample.git
> git checkout -b v1.0.0 v1.0.0
> cd funkload-sample
> git submodule update --init
> virtualenv .venv
> source .venv/bin/activate
> pip install -r requiements.txt
> cd bottle-application-sample
> python migrate.py
> python app.py
```

起動し終えたら、適当にtmuxで新しくセッションを作って
Funkloadのテストケースを実行。

```
cd funkload-sample
> fl-run-bench -c 10 CommentBoard.py CommentBoard.test_index
> fl-build-report --html comment-board-bench.xml
> cd test_index-20130604T175903
> open index.html
> cd ..
> fl-run-bench -c 10 CommentBoard.py CommentBoard.test_posts
> cd -test_posts-20130604T180037
> open index.html
```

起動しているテスト用Webアプリケーションの様子を確認する。

![Comment Board Test Result](https://dl.dropboxusercontent.com/u/614755/qiita/funkload/screen-7.png)

テストで指定したとおりの形できちんとpostできている。
結果も出力されている。

# 使ってみて

Apache Benchより表示は豪華になるが、"傾向を見る"にはいいかもしれない。
負荷のかけかたについて推移を見て、自己判断するとか。

ただ、日本語での情報がちょっと少ないので、
他人が計測した結果などの指標が溜まってなく、
責任が取れる立場じゃないかぎり仕事で使うのは難しいのかなとか思ったりした。
ベンチマークとして判断する限りであれば使えると思うが、
"遅い"という判断をするには自前で"遅い"実装と"速い"実装それぞれ書いたり構築しないといけないし。

Apache Benchはシンプルな分、GETなど参照系でのアプリケーション・サーバー負荷を
確認できるからソッチのほうが運用ノウハウや、ネットを探せばパフォーマンスの結果とかも
あるしそれのがいいのだろうなあ…

ツールは人によりけりだなぁ。