今日のコードリーディング: rakyll/boom
HTTP(S) load generator, ApacheBench (ab) replacement, written in Go - rakyll/boom
概要
Goで書かれたのApacheBenchライクな負荷測定ツール。Python製のtarekziade/boomがオリジナル。
# 合計100リクエストを10並行で実行する
$ boom -n 100 -c 10 https://google.com
バージョン
2015/04/29時点のmaster (372ea3b0e6cb084657d1db7eacccdfae929af5b9)
コード
リクエスト実行部分を中心に必要なところだけ抜粋。
(*Boomer) Run
結果格納用のchannelをつくってrun()
func (b *Boomer) Run() {
b.results = make(chan *result, b.N)
b.run()
close(b.results)
}
(*Boomer) run
リクエストの配分を行うところ。N個のリクエストをC個のworkerで処理している。 NをCで割るんじゃなくてCをworker数として個別のworkerが処理できるだけやる。
func (b *Boomer) run() {
// 総リクエスト数をWaitGroupにAdd
var wg sync.WaitGroup
wg.Add(b.N)
// 総リクエスト分のバッファを持ったchannelを作成
jobs := make(chan *http.Request, b.N)
for i := 0; i < b.C; i++ {
// 並行数分だけworkerを起動
go func() {
b.worker(&wg, jobs)
}()
}
// jobsのchannelに総リクエスト分のリクエストを送信
for i := 0; i < b.N; i++ {
if b.Qps > 0 {
<-throttle
}
jobs <- b.Req.Request()
}
close(jobs)
// 全てのリクエストが処理されるまで待つ
wg.Wait()
}
WaitGroupを各処理でAdd(1)
せずに一度にAdd(N)
しているのはAdd内での排他による待ちを回避したかったからだと思う。
実行数が分かっているものは一度にAddしておくというのはよさそう。
(*Boomer) worker
jobsから受信して実際にリクエスト、結果をb.resultsに送信
func (b *Boomer) worker(wg *sync.WaitGroup, ch chan *http.Request) {
client := &http.Client{Transport: tr}
for req := range ch {
resp, err := client.Do(req)
wg.Done()
b.results <- &result{}
}
}
雑感
- 必要な処理がコンパクトに実装され、非常に読みやすかった
- waitGroupを使ったworkerパターンなどgoroutineの基本的な使い方例として教えるときによさそう
- Add(N)については前述のとおり。
- time.Tickを使ったworkerへのchannel送信の制御(throttleの部分)は覚えておいてよさそう。